Introducing Asynchronous Operations in C # 5

    Last time we talked about how you can simplify working with asynchronous operations using the Jeffrey Richter AsyncEnumerator class , as well as using the Reactive Extensions library . However, as Halesberg and the company recently told us , happiness will come with the release of a new version of the C # language and we will not need to use third-party and not very bicycles when working with asynchronous operations.

    The idea behind asynchronous operations in C # 5 is very similar to the one used by Jeffrey Richter in his AsyncEnumerator class, only this time, besides you and old Richter, the compiler also found out about her (which greatly affects the taste of this syntactic sugar). To begin with, let's return to the synchronous version of our ingenious example that we used earlier:


    1. static void SyncVersion()
    2. {
    3.   Stopwatch sw = Stopwatch.StartNew();
    4.   string url1 = "http://rsdn.ru";
    5.   string url2 = "http://gotdotnet.ru";
    6.   string url3 = "http://blogs.msdn.com";
    7.   var webRequest1 = WebRequest.Create(url1);
    8.   var webResponse1 = webRequest1.GetResponse();
    9.   Console.WriteLine("{0} : {1}, elapsed {2}ms", url1,
    10.     webResponse1.ContentLength, sw.ElapsedMilliseconds);
    11.  
    12.   var webRequest2 = WebRequest.Create(url2);
    13.   var webResponse2 = webRequest2.GetResponse();
    14.   Console.WriteLine("{0} : {1}, elapsed {2}ms", url2,
    15.     webResponse2.ContentLength, sw.ElapsedMilliseconds);
    16.  
    17.   var webRequest3 = WebRequest.Create(url3);
    18.   var webResponse3 = webRequest3.GetResponse();
    19.   Console.WriteLine("{0} : {1}, elapsed {2}ms", url3,
    20.     webResponse3.ContentLength, sw.ElapsedMilliseconds);
    21. }
    * This source code was highlighted with Source Code Highlighter.


    Everything here is very simple (and most importantly very original!): We pretend that we are writing something like our browser, well, or just we have nothing to do and we need to get the contents of three web pages. The synchronous version, as always, works great, except that we wait three times as much as we could by running this operation asynchronously. Since we already realized that this was bad and enjoyed watching Halesberg’s speech, we’ll try right here, just like him, with two mouse strokes and three clicks on the keyboard, to make this wonderful synchronous method ... asynchronous.

    The first thing to do is change the declaration of our function to the following:

    1. static async void AsyncVersion()
    * This source code was highlighted with Source Code Highlighter.


    The async keyword (which in the CTP version is the keyword, and in the release it will be the context keyword) in the method signature indicates that this method runs asynchronously and returns control to the calling code immediately after the start of some asynchronous operation. Asynchronous methods can return one of three return types: void , Task, and Task. If the method returns void , then it will be an asynchronous operation of the type: “start and forget” (“fire and forget”), since it will be impossible to process the result of this operation (although if you do not handle the exceptions inside this method, it will crash nicely, and may well pull the whole process along!). In some scenarios this can be useful (for example, we want to notify all remote subscribers asynchronously and we don't care if they receive these messages or not). With the classes Task and TaskAn inquisitive reader (who is still not completely tired of the pace with which all of us, the beloved company from Redmond is releasing new features), may be familiar, since they have been living and living in the .Net Framework version 4 for some time. The main idea of ​​these classes is that they encapsulate an "incomplete task" and we can wait for it to complete, establish a "continuation" (something that should be called when this task is completed), etc. In this case, the class TaskIt is an inheritor of the Task class and differs from the latter in that it allows you to get a return value of type T through the Result property , while the Task class rather says that some task returns void , and is valuable due to its side effects.

    Since we not only want to run an asynchronous operation, but also get the results of its execution, but the result is not important for us, but a side effect ... In general, we use the Task type as the return value (although we could safely use Taskbut, in general, this is not so important).

    Changing the signature of the method, you need to change a little and its body. For this line of the form:

    1. var webResponse1 = webRequest1.GetResponse();
    * This source code was highlighted with Source Code Highlighter.


    Must be replaced by:

    1. var webResponse1 = await webRequest1.GetResponseAsync();
    * This source code was highlighted with Source Code Highlighter.


    The await keyword (this will also be a contextual keyword in the release) does not mean at all that we will stick at this point in the code until the requested asynchronous operation is completed. This can be confusing (and Eric and the company even suggest looking for other keywords), but it means exactly the opposite idea. The await keyword means that after the start of the asynchronous operation (in this case, after the start of the asynchronous operation of receiving WebResponse ), the method should return control and continue from the same place after the completion of the asynchronous operation. In wise words, this is called sequels, and Eric has a dozen articles.able to wedge great brains about this.

    If you don’t go into the matan, then in this case you can draw a parallel with iterator blocks in C # (if you are not familiar with this topic, I highly recommend that you fix it ), since in both cases the compiler generates a state machine that tracks where it was method execution is interrupted, and is used to correctly restore execution on a subsequent call. However, if in the case of iterators, the execution of the block of iterators resumes during the subsequent call to the MoveNext methodexternal code, the asynchronous method will continue to be executed automatically after the completion of the asynchronous operation. To do this, as a "continuation" of the current task, the current method is set, which is called when the current task is completed.

    So here is the whole modified method in its entirety:

    1. static async Task AsyncVersion()
    2. {
    3.   Stopwatch sw = Stopwatch.StartNew();
    4.   string url1 = "http://rsdn.ru1";
    5.   string url2 = "http://gotdotnet.ru";
    6.   string url3 = "http://blogs.msdn.com";
    7.   
    8.   var webRequest1 = WebRequest.Create(url1);
    9.   Console.WriteLine("Before webRequest1.GetResponseAsync(). Thread Id: {0}",
    10.     Thread.CurrentThread.ManagedThreadId);
    11.  
    12.   var webResponse1 = await webRequest1.GetResponseAsync();
    13.   Console.WriteLine("{0} : {1}, elapsed {2}ms. Thread Id: {3}", url1,
    14.     webResponse1.ContentLength, sw.ElapsedMilliseconds,
    15.     Thread.CurrentThread.ManagedThreadId);
    16.  
    17.   var webRequest2 = WebRequest.Create(url2);
    18.   Console.WriteLine("Before webRequest2.GetResponseAsync(). Thread Id: {0}",
    19.     Thread.CurrentThread.ManagedThreadId);
    20.  
    21.   var webResponse2 = await webRequest2.GetResponseAsync();
    22.   Console.WriteLine("{0} : {1}, elapsed {2}ms. Thread Id: {3}", url2,
    23.     webResponse2.ContentLength, sw.ElapsedMilliseconds,
    24.     Thread.CurrentThread.ManagedThreadId);
    25.  
    26.   var webRequest3 = WebRequest.Create(url3);
    27.   Console.WriteLine("Before webRequest3.GetResponseAsync(). Thread Id: {0}",
    28.     Thread.CurrentThread.ManagedThreadId);
    29.   var webResponse3 = await webRequest3.GetResponseAsync();
    30.   Console.WriteLine("{0} : {1}, elapsed {2}ms. Thread Id: {3}", url3,
    31.     webResponse3.ContentLength, sw.ElapsedMilliseconds,
    32.     Thread.CurrentThread.ManagedThreadId);
    33. }
    * This source code was highlighted with Source Code Highlighter.


    (To be honest, I also changed the output to the console so that it was visible in which thread the method is currently running, but this is for the sake of clarity, so this change does not count. But in any case, old Halesberg did not lie, the changes , indeed, the minimum amount).

    And this is how this method is called:

    1. static void Main(string[] args)
    2. {
    3.   
    4.   try
    5.   {
    6.     Console.WriteLine("Main thread id: {0}", Thread.CurrentThread.ManagedThreadId);
    7.     var task = AsyncVersion();
    8.     Console.WriteLine("Right after AsyncVersion() method call");
    9.     //Ожидаем завершения асинхронной операции
    10.     task.Wait();
    11.     Console.WriteLine("Asyncronous task finished!");
    12.     
    13.   }
    14.   catch(System.AggregateException e)
    15.   {
    16.     //Все исключения в TPL пробрасываются обернутые в AggregateException
    17.     Console.WriteLine("AggregateException: {0}", e.InnerException.Message);
    18.   }
    19.   Console.ReadLine();
    20. }
    * This source code was highlighted with Source Code Highlighter.


    And here is the result of its execution:

    Main thread id: 10

    Before webRequest1.GetResponseAsync (). Thread Id: 10


    Right after AsyncVersion () method call


    rsdn.ru : 1672, elapsed 657ms. Thread Id: 13

    Before webRequest2.GetResponseAsync (). Thread Id: 13


    gotdotnet.ru : 99470, elapsed 1915ms. Thread Id: 14

    Before webRequest3.GetResponseAsync (). Thread Id: 14


    blogs.msdn.com : 47927, elapsed 2628ms. Thread Id: 15

    Asynchronous task finished!


    Now let's take a look at what is happening inside this beast. So, the AsyncVersion method is called in the current thread, and control returns immediately after the first await statement . But before returning control to the calling code, the AsyncVersion method is set as a “continuation” of the current task and the place from which to continue execution is remembered. This function returns an object of type Task, so that we can wait for completion and check the results. Then, after the completion of the asynchronous operation, execution resumes from the previous place and, as we see, in another thread. Further, this process continues until the third asynchronous operation is completed, after which the task.Wait method will return control, and we will see the cherished on the console: “Asynchronous task finished!”.

    Error handling has also undergone some changes, but also very minor ones. If you are already familiar with TPL, then you will recognize the familiar System.AggregateExcpetion classwhich “collects” all exceptions and accumulates them inside itself. The reason for this is that one task may have a dozen child tasks, each of which may contain several more "subtasks", and each task from this "tree" of tasks may fall with its own exception. The AggregateException serves as the solution to this problem . It contains an “tree” of exceptions that can be easily “straightened” using the Flatten method (for details, see, for example, the section “Working with AggregateException”). In general, if the error handling has not become much more complicated, and comparing this with the nightmare that one has to deal with when working with APM, then such a “complication”, even calling the fingers will not turn into a problem.

    In fact, this example is not very different from synchronous; because in a row we perform three asynchronous operations, and each subsequent operation begins only after the previous one is completed. Let's try to rewrite in a new form our second example, which simultaneously receives results from three web pages and writes the result to a file asynchronously:

    1. public static async void AsyncVersion2()
    2. {
    3.   Stopwatch sw = Stopwatch.StartNew();
    4.   var urls = new string[] {"http://rsdn.ru", "http://gotdotnet.ru",
    5.     "http://blogs.msdn.com"};
    6.   var tasks = (from url in urls
    7.         let webRequest = WebRequest.Create(url)
    8.         select new {Url = url, Response = webRequest.GetResponseAsync()})
    9.         .ToList();
    10.   var data = await TaskEx.WhenAll(tasks.Select(t=>t.Response));
    11.   var sb = new StringBuilder();
    12.   foreach(var s in tasks)
    13.   {
    14.     sb.AppendFormat("{0}: {1}, elapsed {2}ms. Thread Id: {3}", s.Url,
    15.       s.Response.Result.ContentLength,
    16.       sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId)
    17.       .AppendLine();
    18.   }
    19.   var outputText = sb.ToString();
    20.   Console.WriteLine("Web request results: {0}", outputText);
    21.       
    22.   using (var fs = new FileStream("d:\\results.txt", FileMode.Create,
    23.       FileAccess.Write, FileShare.Write))
    24.   {
    25.     await
    26.     fs.WriteAsync(UnicodeEncoding.Default.GetBytes(outputText), 0,
    27.             UnicodeEncoding.Default.GetByteCount(outputText));
    28.   }
    29.       
    30. }
    * This source code was highlighted with Source Code Highlighter.

    This is already really interesting! We got a completely asynchronous code, but it doesn’t look like a herd of Indians worked on it for several nights (and an asynchronous code of similar complexity looks exactly like that). It is understandable and reads similarly synchronous! (If anyone does not believe, then let him try to rewrite this code in the classic APM style).

    So what do we have? We have a thing that is really convenient to use without fear of shooting your own leg. In addition, without even going into serious jungle, it is more or less clear how to use this business (well, although you will have to get used to the names). And the most interesting thing is that this is far from all the functionality. I did not talk about synchronization contexts and how this business can be used with the user interface thread, what TAP (Task-based Asynchronous Pattern) is, I did not say that all the new asynchronous methods that I used are extension methods, and you can add them yourself as much as you like, and you didn’t go into the jungle of implementation ... But that's all, about it some other time! And today, I suggest you just download this thing and try it yourself!

    (I almost forgot, here is the url so that you do not look for it for a long time: msdn.com/vstudio/async)

    Also popular now: