Wednesday, July 27, 2011 12:26 PM
most likely I'm doing something completely wrong. So please be patient and just assume that I'm not the brightest light around ;)
I tried to create my first "usefull" app using the new async extensions: Reading some stuff out of a website to automate some tasks. See code below.
I had a Progressbar in Marquee Mode on my form and so I immediatley noticed that the UI thread was blocked for some time, when I called my function. I assumed that I had some blocking synchronous call inside so I used the performance wizard to show me where to start searching. I was surprised that it was GetRequestStreamAsync that blocked for around 10-20 seconds.
Can somebody explain this behavior?
Private Async Function Login() As Tasks.Task(Of Boolean) Dim wr As HttpWebRequest = HttpWebRequest.Create("https://xxxxxxx/login.php") Dim result As String = String.Empty With wr ' set up the WR .CookieContainer = Cookies .Method = "POST" .ContentType = "application/x-www-form-urlencoded" .Referer = "https://xxxxxxx/index.php" .AllowAutoRedirect = True Dim b() As Byte = System.Text.Encoding.ASCII.GetBytes("login_pass_inputbox=" & Me.Password & "&login_user_inputbox=" & Me.UserID) .ContentLength = b.Count ' Here ist the blocking part - just WHY? Dim RS As System.IO.Stream = Await .GetRequestStreamAsync With RS Await .WriteAsync(b, 0, b.Count) .Flush() .Close() End With ' call server to get a response With Await .GetResponseAsync() With New IO.StreamReader(.GetResponseStream) result = Await .ReadToEndAsync() End With End With End With If result.IndexOf("xxxxxxxx", System.StringComparison.InvariantCultureIgnoreCase) < 0 Then Return False Return True End Function
EDIT: Of course, I must be stupid ;)
The calling function
Public Async Function ReadAsync(ByVal Progress As ProgressDelegate) As Tasks.Task(Of Boolean) Cookies = New CookieContainer Dim t As New Tasks.TaskFactory(Of Boolean) If Progress IsNot Nothing Then Progress("Login to KonsoleH", 10) ' First version 'If Not Await Login() Then Return False ' Second version: non blocking If Not Await Tasks.TaskEx.RunEx(AddressOf Login) Then Return False If Progress IsNot Nothing Then Progress("Reading Domains", 20) Return True End Function
The second version works without blocking the UI thread. Though I thought that async was meant to be a fire-and-forget solution for dummies? I did not expect that there are "async" functions that still block for some reason?
Saturday, July 30, 2011 12:50 AMModerator
The Async CTP's GetRequestStreamAsync and GetResponseAsync are simple wrappers around the existing HttpWebRequest.BeginGetRequestStream and BeginGetResponse in .NET 4. Those Begin* methods have a lot of setup work they do (e.g. proxy, DNS, connection pooling, etc.) before they can submit a request, and unfortunately today that work all happens synchronously as part of the Begin* call. If there's something in your environment that could be delaying one of those synchronous activities, that could be causing the visible delay you've noticed. The workaround for now, as you've discovered, is to wrap such slow invocations in another task that will move that synchronous work off to the thread pool so that it doesn't block your UI thread.
Monday, August 01, 2011 10:43 PM
Yes HttpWebRequest has a lot of setup work to do, but there is something wrong with the machine/environment if that setup work is taking multiple seconds. The most likely candidates are DNS and proxies. You can try the following to help narrow it down:
1. Measure how long System.Net.Dns.GetHostEntry("destinationServer") takes.
2. Measure how long System.Net.WebRequest.DefaultWebProxy.GetProxy(new Uri("http://destinationServer")) takes.
3. Enable System.Net tracing with timestamps to see at what point the request gets stuck for so long. See http://msdn.microsoft.com/en-us/library/ty48b824.aspx
Wednesday, August 03, 2011 3:44 PM
Stephen, thanks for the answer. Though I still think, it might not be the best idea to mark something with "Async" if it still can block for some time. Maybe something like "FunctionNamePartlyAsync" fits better ;)
I mean: If I SEE a function name decorated with "Async", I (and probably not only me) usually expect that the call returns asap. Isn't that simply the basic idea behind all that async stuff?
Tratcher: Maybe there is something "wrong" with my network setup - but it still works ;)
Thursday, August 04, 2011 6:42 PM
Are there plans to make BeginGetRequestStream/BeginGetResponse truly asynchronous? If not, would Async CTP consider RunEx wrappers instead of FromAsync wrappers for these?
IMO, if an operation is slow because of CPU usage, then it's the end user's job to use RunEx. But if an operation is slow because of I/O, then the framework should provide a truly asynchronous *Async method.
Programming blog: http://nitoprograms.blogspot.com/
Including my TCP/IP .NET Sockets FAQ
and How to Implement IDisposable and Finalizers: 3 Easy Rules
Microsoft Certified Professional Developer
How to get to Heaven according to the Bible
Thursday, August 04, 2011 8:22 PMNo, there are not currently plans to re-write BeginGetRequestStream or BeginGetResponse. We are investigating alternatives for the Task versions of these APIs.