How to use Async and Await ?

Using (or providing) Microsoft.NET Classes

How to use Async and Await ?

Postby PGilbert on Thu Dec 17, 2015 12:38 am

Looks like Microsoft is promoting the Async and Await methods in .Net 4.5 for asynchronous programming (see here) while using only one thread. Can we use those 2 methods with Dyalog 14.1 ? In the affirmative could it be possible to have a simple example. In the negative what is Dyalog recommending for Asynchronous programming while using .Net (simple example of that would be nice too).

Thanks in advance.
User avatar
PGilbert
 
Posts: 436
Joined: Sun Dec 13, 2009 8:46 pm
Location: Montréal, Québec, Canada

Re: How to use Async and Await ?

Postby JohnD|Dyalog on Fri Dec 18, 2015 12:38 pm

PGilbert wrote:Looks like Microsoft is promoting the Async and Await methods in .Net 4.5 for asynchronous programming (see here) while using only one thread. Can we use those 2 methods with Dyalog 14.1 ? In the affirmative could it be possible to have a simple example. In the negative what is Dyalog recommending for Asynchronous programming while using .Net (simple example of that would be nice too).

Thanks in advance.


Hello Pierre,

async and await are fascinating things. Very powerful, and to a certain extent map onto "futures" which we have in Dyalog. Both async and await are keywords in C#, not functions, and so are not available to you in Dyalog. However, some of the functionality can be emulated. await is usually applied to a value which is an instance of a Task<> (https://msdn.microsoft.com/en-us/library/dd321424.aspx). A Task<> is a little like one of our futures, in that it has a value which may not be available yet. Calling a function which returns a Task<> starts the process of calculating the result but does not block waiting for it. Simplistically await waits for the result to be available, which in Dyalog can be done by retrieving its Result property (with futures, Dyalog automatically blocks when the future is passed to a function which needs the actual value). In C# await is more complicated though, in that it can cause the result of the calling function, if is declared as async , to also be a Task<> and the async state can become sort of nested.

Currently we have no plans to implement complete support for async and await, although it's possible that in a later version of Dyalog we might wrap Task<>s in futures and handle the waiting in the interpreter.

Implementing async in Dyalog would be significantly more complicated, and I can't see a way to emulate it in APL.

Best Regards,
John Daintree
User avatar
JohnD|Dyalog
 
Posts: 74
Joined: Wed Oct 01, 2008 9:35 am

Re: How to use Async and Await ?

Postby PGilbert on Sat Dec 19, 2015 4:49 pm

Many thanks John for the detailed answer. I will experiment with Task<> and wait for it's results on another thread to see if we can achieve asynchronous programming without too much effort.
User avatar
PGilbert
 
Posts: 436
Joined: Sun Dec 13, 2009 8:46 pm
Location: Montréal, Québec, Canada

Re: How to use Async and Await ?

Postby PGilbert on Tue Dec 29, 2015 1:44 am

Hello John, I am having some problem catching the ⎕EXCEPTION while using the Task<>. Considering the following two functions:

Code: Select all
 ConnectAsync(ip port);⎕USING

 ⎕USING←'System.Net,System.dll' 'System.Net.Sockets,System.dll'

 :Trap 0

     tcp←⎕NEW TcpClient
     task←tcp.ConnectAsync(ip port)

     :If 0=⎕TSYNC task.Wait&(1000)
         ⎕←'Failure'
         ⎕EXCEPTION.Message

     :Else
         ⎕←'Success'
     :End

 :Else
     ⎕←'Failure in Trap'
     ⎕EXCEPTION.Message
 :End

Code: Select all
 Connect(ip port);⎕USING

 ⎕USING←'System.Net,System.dll' 'System.Net.Sockets,System.dll'

 :Trap 0

     tcp←⎕NEW TcpClient
     tcp.Connect(ip port)

     ⎕←'Success'

 :Else
     ⎕←'Failure'
     ⎕EXCEPTION.Message
 :End

Connect will connect the standard way and ConnectAsync will use a Task<> and do the blocking on another thread so the main calling thread will not be block (neat). Now if you execute:
      ConnectAsync('www.dyalog.org' 80)
Failure
Failure in Trap
Object reference not set to an instance of an object.

Connect('www.dyalog.org' 80)
Failure
No such host is known


The function ConnectAsync will not catch the ⎕EXCEPTION while the function Connect will catch it. Question: Is there a way to catch the exception in ConnectAsync ?

Thanks in advance
User avatar
PGilbert
 
Posts: 436
Joined: Sun Dec 13, 2009 8:46 pm
Location: Montréal, Québec, Canada

Re: How to use Async and Await ?

Postby PGilbert on Tue Dec 29, 2015 1:14 pm

Looks like the exception is in the property 'Exception' of 'task'. My problem now is that I am getting a VALUE ERROR at line [11] when the function is normally running but if I put a STOP at line [11] and execute in the session task.Exception.GetBaseException.Message it is working.

      ∇ConnectAsync[⎕]∇
[0] ConnectAsync(ip port);⎕USING
[1]
[2] ⎕USING←'System.Net,System.dll' 'System.Net.Sockets,System.dll'
[3]
[4] :Trap 0
[5]
[6] tcp←⎕NEW TcpClient
[7] task←tcp.ConnectAsync(ip port)
[8]
[9] :If 0=⎕TSYNC task.Wait&(1000)
[10] ⎕←'Failure'
[11] ⎕←task.Exception.GetBaseException.Message
[12]
[13] :Else
[14] ⎕←'Success'
[15] :End
[16]
[17] :Else
[18] ⎕←'Failure in Trap'
[19] ⎕EXCEPTION.Message
[20] :End


Question: How do I make work line [11] in a normally running program ?
User avatar
PGilbert
 
Posts: 436
Joined: Sun Dec 13, 2009 8:46 pm
Location: Montréal, Québec, Canada

Re: How to use Async and Await ?

Postby Tomas Gustafsson on Tue Jan 05, 2016 2:00 am

Hi Pierre, maybe you're a bit overcomplexy! :-)

I don't have precise answers for you but

1. The tcpip connect probably has a longer tiomeout than 1000 ms, by default. Meaning maybe there IS no exception handy yet.
2. Really, you shouldn't actively sit there and wait, instead jump out, do other stuff, and let Windows call you! It will, for sure!

I'll post a coupla fns that might help. These are for retrieving files using Http GET, but basics are similar. Most essentially, you define a callback function by using ⎕OR, tell Windows to async, jump out AND expect Windows to call back ANY TIME - when that happens, JohnD helpfully (and with no further permission asked) spawns a new APL thread in your computer so get ready for out of sync (well use globals, that's what i do). Have a look below. I didn't alter to make'em explanatory, just removed some security&sensitive. Hope you get the clues anyway!

      R←Async_Get(job bytes url_spaces FN);i;httpSite;ns;o;r;url
⍝ job : Explaining sentence, eg. 'File 2 of 7'
⍝ bytes : Bytes to receive, 0 if unknown
⍝ url : Eg. 'http://blabla.com/subdir/file.ext'
⍝ FN : Function to call (through que) when ready

⍝ Note: Works in #.UPDATE (not in #.HTTP)
⍝ Note: Customized for Update
⍝ Note: Supports multiple simultaneous retrieves, but we use only one at time for now

⍝ R: id

⍝ ⎕USING←,⊂'System,system.dll'
⍝ ⎕USING,←⊂'System.Net,system.net.dll'

⍝ Replace spaces in url with %20
url←{~∨/a←' '=⍵:⍵ ⋄ b←1↓¨(1,a)⊂' ',⍵ ⋄ 3↓∊(⊂'%20'),¨b}url_spaces

:If 0=⎕NC'#.UPDATE.ar'
#.UPDATE.ar←#.AsyncCallback.New ⎕OR'#.UPDATE.Async_RespCallback'
#.UPDATE.rc←#.AsyncCallback.New ⎕OR'#.UPDATE.Async_ReadCallback'
:End
⍝ Find an id for this instance
⍝ Must be global towards a unique get op, since figures may cover multiple get's
ns←⍎('#.UPDATE._',⍕#.UPDATE.ASYNC,←i←⊃(⍳1+⌈/#.UPDATE.ASYNC,0)~#.UPDATE.ASYNC)⎕NS'' ⍝ and create a namespace for this data, eg. #.UPDATE._2

⍝ If the id became bigger than counter variables sizes, enlarge variables
:If i>⊃⍴#.UPDATE.BYTES ⋄ #.UPDATE.JOB,←⊂,'' ⋄ #.UPDATE.BYTES⍪←0 0 ⋄ #.UPDATE.LAST,←⊂,'' ⋄ #.UPDATE.ABORTED,←0
:End

#.UPDATE.JOB[i]←⊂job ⋄ #.UPDATE.BYTES[i;]←0,bytes ⋄ #.UPDATE.LAST[i]←,⊂'' ⋄ #.UPDATE.ABORTED[i]←0

httpSite←#.Uri.New url

o←ns.rs←⎕NEW #.Object ⍝ RequestState (cannot be local to this function, neither can it's elements)
o.BufferRead←#.Array.CreateInstance #.Byte 8192 ⍝ BufferRead - mandatory, .net modifies it's data
o.requestData←⎕NEW #.IO.MemoryStream ⍝ requestData - not mandatory, but they like to rs as a carrier
o.streamResponse←⎕NULL ⍝ streamResponse - mandatory, .net modifies it's data
o.request←#.WebRequest.Create httpSite ⍝ request - ?
o.request.UserAgent←'Your app'
o.request.Proxy←⎕NULL

o.namespace←ns ⍝ The namespace holding objects and data

o.ARES←0 ⍝ Result, 0/1
o.ADATA←,'' ⍝ Result data
o.FN←FN

o.ASYNCROW←i ⍝ Row in counter variables for this get task

r←o.request.BeginGetResponse #.UPDATE.ar o

R←i ⍝ Give id to calling function


      Async_RespCallback arg;rs;resp

:Trap 0
rs←arg.AsyncState ⍝ Get rs (== the original object)
resp←rs.request.EndGetResponse arg
rs.streamResponse←resp.GetResponseStream
{}rs.streamResponse.BeginRead rs.BufferRead 0 8192 #.UPDATE.rc rs

:Else
rs.ADATA←{(¯1+⍵⍳⎕TC[3])↑⍵}⍕⎕EXCEPTION
#.SYS.injectQue rs.FN 63 rs.namespace 0 ⍝ Inject call to result handler
:End


      Async_ReadCallback arg;a;len;rs

:Trap 0
rs←arg.AsyncState ⍝ Get rs (== the original object)

:If 0<len←rs.streamResponse.EndRead arg ⍝ Bytes remaining
#.UPDATE.BYTES[rs.ASYNCROW;1]+←len
rs.requestData.Write rs.BufferRead,0,len ⍝ Append rs.BufferRead -> rs.requestData
a←rs.streamResponse.BeginRead rs.BufferRead 0 8192 #.UPDATE.rc rs ⍝ Re-start read into rs.BufferRead
→0
:Else
rs.ADATA←82 ⎕DR{⍵-256×⍵>127}rs.requestData.ToArray ⍝ From signed ⎕DR 83 to (unsigned) ⎕DR 82
rs.ARES←1 ⍝ Successful!
:End
:Else
rs.ADATA←{(¯1+⍵⍳⎕TC[3])↑⍵}⍕⎕EXCEPTION
:End

rs.streamResponse.Close ⋄ rs.streamResponse.Dispose
rs.requestData.Close ⋄ rs.requestData.Dispose
#.SYS.injectQue rs.FN 63 rs.namespace 0 ⍝ Pass to game loop for immediate execution in next frame


It all starts with a
      R←#.UPDATE.Async_Get('Retrieving update file "',a,'"')size url'#.UPDATE.GetFiles_h' ⍝ Go!


Each time Async_ReadCallback is ready, it will inject (the "#.SYS.injectQue") a task to the game loop which may or may not initiate a new async retrieve. That's essentially the same as if you'd ⎕NQ nn to your form.Wait and react on that by re-starting (to get next file in a list, likewise).

Note that the Resp and Read functions both contain ⎕EXCEPTION!

Specifically note that none of these functions hold.

Windows async stuff is very powerful, as John noted. A nice thing is that the OS goes for the cores.
Tomas Gustafsson
 
Posts: 101
Joined: Mon Sep 19, 2011 6:43 pm

Re: How to use Async and Await ?

Postby JohnD|Dyalog on Tue Jan 05, 2016 9:36 am

Hello Pierre (and Thomas),

As Thomas has pointed out the 1st example probably failed because a 0 return from task.Wait only indicates that the Task has not completed, there may not be an exception. The same should be true in the 2nd case too. task.Exception will be a VALUE ERROR (will return []NULL in Dyalog 15.0), if Wait returns because the Task has not completed. I suspect that that is what is happening when you run the function normally. When you have the stop in place, I suspect that something is happening in the Task after the return from Wait, but before you type the expression in the session. You say that task.Exception.GetBaseException.Message "works" after the stop. What message does it return?

Best Regards
John Daintree.
User avatar
JohnD|Dyalog
 
Posts: 74
Joined: Wed Oct 01, 2008 9:35 am

Re: How to use Async and Await ?

Postby PGilbert on Tue Jan 05, 2016 4:48 pm

Hello Thomas and John. Thomas thanks for sharing your code.

Tomas Gustafsson wrote:2. Really, you shouldn't actively sit there and wait, instead jump out, do other stuff, and let Windows call you! It will, for sure!


The way you do with callbacks and BeginXXX is the current way to do asynchronous functions. I am trying to explore if it is possible to use the relatively new commands XXXAsync with Dyalog. If it is possible, it should make the coding of asynchronous functions much easier to do and it could be used for TCP, UDP, serial, file operation, HTTP, etc.. The idea is to do the waiting on another thread so the calling thread will not block (internally the XXXAsync command is using the BeginXXX command and creating a callback like in your code). So far the results are encouraging and if successful I will share them on the Wiki. If not successful, I will have to learn how to do it with callbacks like you just show us.

Tomas Gustafsson wrote:1. The tcpip connect probably has a longer timeout than 1000 ms, by default. Meaning maybe there IS no exception handy yet.


Thomas if you try my example with a longer delay it is still not working. John is correct to say that a task that returns a 0 does not mean there is an error. For example while doing a .ReadAsync if the task returns 0, it means that the task did not complete because nothing was received and it is normal to have no EXCEPTION available. In the case of .ConnectAsync if the task did not complete on time (supposing there was enough time to complete it) there is an EXCEPTION.

My point is that if you try the following code in the session it is working as expected, but if you run the same code in a function I am getting a VALUE ERROR when trying to retrieve the Exception.

      ⎕USING←'System.Net,System.dll' 'System.Net.Sockets,System.dll'
tcp←⎕NEW TcpClient
task←tcp.ConnectAsync('192.168.1.9' 2001) ⍝ Put here an IP address that does not exists on your network
⎕TSYNC task.Wait&(5000) ⍝ Task did not complete within 5 sec which is normal since the IP does not exists.
0
task.(IsFaulted IsCompleted IsCanceled) ⍝ There is no fault (IP is properly formed and port number is within range)
0 0 0
task.Exception.GetBaseException.Message ⍝ And here is the correct exception
A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 192.168.1.9:2001


Now if you put that same code into a function:
      ConnectAsync(ip port);⎕USING

⎕USING←'System.Net,System.dll' 'System.Net.Sockets,System.dll'

:Trap 0

tcp←⎕NEW TcpClient
task←tcp.ConnectAsync(ip port)

:If 0=⎕TSYNC task.Wait&(5000) ⍝ Wait no more than 5 seconds for the task to complete
⎕←'Failure'
task.Exception.GetBaseException.Message
:Else
⎕←'Success'
:End

:Else
⎕←'Failure in Trap'
⍝ Show the error.
:If 90=⎕EN
⍝ .Net Error
⎕←⎕EXCEPTION.GetBaseException.Message
:Else
⍝ APL Error
⎕←(1⊃⎕DM),': ',{(' '=1↑⍵)↓((1↓a,0)∨a←' '≠⍵)/⍵}(2⊃⎕DM)
:EndIf
:End

and execute that function with an IP address that does not exists I get:
      ConnectAsync '192.168.1.9' 2001
Failure
Failure in Trap
VALUE ERROR: ConnectAsync[11] task.Exception.GetBaseException.Message


There is a VALUE ERROR when requesting task.Exception while when we do the same code in the session we have the proper Exception.

My question is: Why task.Exception is empty while executing the function ConnectAsync at line [11] when it is not empty when executing the same code manually in the session ?
User avatar
PGilbert
 
Posts: 436
Joined: Sun Dec 13, 2009 8:46 pm
Location: Montréal, Québec, Canada

Re: How to use Async and Await ?

Postby Tomas Gustafsson on Wed Jan 06, 2016 12:57 am

Ya i see the point Micro$oft makes there. Why not, actually. Indeed it does remove the need to manualy call the async cycling (callback fn, re-enter... until all done, then trick the result into useful manageable housekeeping).

The relevant difference, really, is that await grabs and keeps an image of the current stack at the moment await happens - AND the system execution (main/other thread exec pointer) is able to jump into that stack later on and continue. Right, John?

As opposed to current (for example APL) implementations, where going into an async-seeding function is "slippery" - we slip out from it right away, no traces left. await again takes a snapshot of the stack. Perhaps that *could* be implemented in APL? Namely, if the function holding the await wasn't initially a separate &-thread, maybe Dyalog could *make* it a separate &-thread at that very point await happens? Then just hibernate it (until .net says go on) and continue execution from where the function was called. Requires a bit of mambo jambo, i guess.

Another issue is maintaining update status etc. How do you grab the n in "item n of 42 completed"? How do you interrupt the async loop at it's internal, hidden level? .net must provide methods to do this. Maybe it does?

Anyway, i could propose one thing for your current Q, Pierre: Would a simple #.Application.DoEvents help? Since if .net loops the async internally, maybe it still uses messages, or maybe the exception is passed via some message mechanism? Maybe there's nothing in
⎕TSYNC task.Wait&(5000)
that rolls the events, while maybe there is when the session is standby?

Not likely, but always worth trying :-). Ofc it means you have to implement the Wait some other way, split it up, etc.


------------------- edit -------------------
Oh, gotta edit this. Try with 20000 milliseconds :-). I get:

ConnectAsync '193.123.11.11' 47000
Failure
Anslutningsförsöket misslyckades eftersom den anslutna datorn inte svarade inom en viss tid eller på grund av att den etablerade anslutningen till värddatorn inte längre fungerar 193.123.11.11:47000

Ie. a fully valid exception.
Tomas Gustafsson
 
Posts: 101
Joined: Mon Sep 19, 2011 6:43 pm

Re: How to use Async and Await ?

Postby Vince|Dyalog on Wed Jan 06, 2016 12:00 pm

Hi Pierre and Tomas,

In the Task Class documention, it says that task.Exception will return null if the task completed successfully or has not yet thrown any exceptions. It is this return null which leads to the value error. If you put a ⎕DL 20 before asking for task.Exception in the function, you'll get the exception itself.

I think that Tomas is right in that you need to pass a longer time in task.Wait.

Regards,

Vince
Vince|Dyalog
 
Posts: 412
Joined: Wed Oct 01, 2008 9:39 am

Next

Return to Microsoft.NET

Who is online

Users browsing this forum: No registered users and 1 guest