How to use Async and Await ?
23 posts
• Page 1 of 3 • 1, 2, 3
How to use Async and Await ?
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.
Thanks in advance.
-
PGilbert - Posts: 440
- Joined: Sun Dec 13, 2009 8:46 pm
- Location: Montréal, Québec, Canada
Re: How to use Async and Await ?
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
-
JohnD|Dyalog - Posts: 74
- Joined: Wed Oct 01, 2008 9:35 am
Re: How to use Async and Await ?
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.
-
PGilbert - Posts: 440
- Joined: Sun Dec 13, 2009 8:46 pm
- Location: Montréal, Québec, Canada
Re: How to use Async and Await ?
Hello John, I am having some problem catching the ⎕EXCEPTION while using the Task<>. Considering the following two functions:
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:
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
- 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
-
PGilbert - Posts: 440
- Joined: Sun Dec 13, 2009 8:46 pm
- Location: Montréal, Québec, Canada
Re: How to use Async and Await ?
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.
Question: How do I make work line [11] in a normally running program ?
∇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 ?
-
PGilbert - Posts: 440
- Joined: Sun Dec 13, 2009 8:46 pm
- Location: Montréal, Québec, Canada
Re: How to use Async and Await ?
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!
It all starts with a
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.
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 ?
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.
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.
-
JohnD|Dyalog - Posts: 74
- Joined: Wed Oct 01, 2008 9:35 am
Re: How to use Async and Await ?
Hello Thomas and John. Thomas thanks for sharing your code.
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.
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.
Now if you put that same code into a function:
and execute that function with an IP address that does not exists I get:
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 ?
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 ?
-
PGilbert - Posts: 440
- Joined: Sun Dec 13, 2009 8:46 pm
- Location: Montréal, Québec, Canada
Re: How to use Async and Await ?
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.
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 ?
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
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: 432
- Joined: Wed Oct 01, 2008 9:39 am
23 posts
• Page 1 of 3 • 1, 2, 3
Who is online
Users browsing this forum: No registered users and 1 guest
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group