Can you summarize all asynchronous programming models

Asynchronous programming with async and await

  • 8 minutes to read

The task-based asynchronous programming model provides an abstraction via asynchronous code. You can write code as a series of instructions in the usual way. You can read this code as if each statement completes before the next statement begins. The compiler performs several transformations because some of these statements might start and then return a task that represents the work that is currently being done.

That is the goal of this syntax: to activate code that reads like a sequence of instructions, but runs in a much more complicated order based on an external resource allocation and task completion. This is similar to the way humans issue instructions for processes that contain asynchronous tasks. In this article, you'll use breakfast preparation instructions as an example to learn how to use the keywords and make it easier to analyze code that contains a series of asynchronous instructions. To explain how to prepare a breakfast, you would write instructions and your list would look something like this:

  1. Pour yourself a cup of coffee.
  2. Heat a pan and fry two eggs in it.
  3. Fry three slices of bacon.
  4. Toast two slices of bread.
  5. Brush the toasted bread with butter and jam.
  6. Pour yourself a glass of orange juice.

If you have experience in cooking, you would use these instructions asynchronous To run. You would first heat the pan for the eggs and then start with the breakfast bacon. You would put the bread in the toaster and then start with the eggs. At each step of the process, you would start a task and then turn your attention to tasks that are ready for your attention.

Preparing breakfast is a good example of asynchronous work that is not done in parallel. One person (or thread) can do all of these tasks. To stick with the breakfast example, a person can prepare breakfast asynchronously by starting the next task before the first task is completed. Preparation is progressing, regardless of whether someone is keeping an eye on it or not. Once you start heating the pan for the eggs, you're ready to start frying the breakfast bacon. After you've started frying the breakfast bacon, you can stick the bread in the toaster.

For a parallel algorithm you would need several cooks (or threads). One cook would take care of the eggs, another would take care of the breakfast bacon, and so on. Each cook would only focus on this one task. Each cook (or thread) would be blocked synchronously while it waits for the breakfast bacon to be turned or for the toaster to throw out the bread.

Now look at the same statements as C # statements:

It took about 30 minutes to cook breakfast in sync. This duration corresponds to the sum of the individual tasks.

Note

The classes,,, and are empty. They are only marker classes for demonstration purposes and do not contain any properties.

Computers interpret these instructions differently than humans. After each instruction, the computer blocks further action until the work is completed. Only then does he continue with the next instruction. This would not make a tasty breakfast. The later tasks would not start until the earlier tasks were completed. It would take much longer to prepare breakfast and some components would be cold again by the time they are served.

If you want the computer to perform the above instructions asynchronously, you must write asynchronous code.

These considerations are important in the programs you write today. When you write client programs, you want the user interface to be responsive to user input. Your application should not give the impression that the smartphone has hung up while it is downloading data from the web. When you write server programs, you don't want threads to be blocked. These threads could be used for other requirements. Using synchronous code when there are asynchronous alternatives affects your options for cheaper extensions. You pay for the blocked threads.

Successful modern applications require asynchronous code. Without language support, writing asynchronous code required callbacks, completion events, or other methods that obscured the code's original intent. The advantage of synchronous code is that performing the actions step-by-step makes it easier to scan and understand. With traditional asynchronous models, you had to focus on the asynchronous properties of the code rather than the basic actions of the code.

Don't block, use "await" instead

The above code shows bad practice: creating synchronous code to perform asynchronous operations. As it stands, this code prevents the thread from doing any other work. It will not be interrupted while any of the other tasks are in progress. This would be like staring at the toaster after putting the bread in it. You would not be accessible to anyone until the toasted bread was ejected.

So let's update this code so that the thread is not blocked while tasks are in progress. The keyword provides the ability to start a task and then continue executing when that task is complete without blocking. A simple asynchronous version of the code for breakfast preparation would therefore look like the following code snippet:

Important

The total elapsed time is roughly the same as the initial synchronous version. The code has yet to be designed to take advantage of some important features of asynchronous programming.

tip

The, and method bodies have been updated so they now return, and. The methods are renamed and then contain the suffix “Async”. Their implementations are shown as part of the final version later in this article.

This code will not block while the eggs or bacon are being fried. However, this code does not start any other tasks. You would continue to put the bread in the toaster and stare at the device until the bread is ejected. But you would at least be available to other people who want your attention. In a restaurant where multiple orders are being placed, the cook could begin preparing another breakfast while the first breakfast is being prepared.

Now the breakfast preparation thread is not blocked while waiting for started tasks that have not yet been completed. For some applications this change is sufficient. This change alone means that a GUI application continues to react to the user. However, in this scenario, you want more. The individual components or tasks should not be carried out sequentially. It is better to start each component / task without waiting for the previous task to complete.

Start tasks at the same time

In many scenarios, you want to start several independent tasks immediately. Once one of the tasks is completed, you can then resume other tasks that are ready. Sticking to the example of breakfast, you would be able to prepare it much faster. In addition, you can complete all tasks almost at the same time. And so get a hot breakfast.

System.Threading.Tasks.Task and related types are classes that you can use to analyze tasks that are in progress. This allows you to write code that is much more like the way you actually make breakfast. You would start making eggs, bacon, and toast all at the same time. Since this requires one action at a time, you would focus your attention on that task, move on to the next action, and then wait for something else that needs your attention.

You start a task and then keep the Task object that represents the work. You wait for each task (“”) before working with its result.

Now let's make the appropriate changes to the code for breakfast. The first step is to save the tasks for operations when they start instead of waiting for them:

Next, you can move the bacon and egg instructions to the end of the method before breakfast is served:

It took about 20 minutes to cook breakfast asynchronously. This time saving can be explained by the fact that some tasks were executed at the same time.

The above code works better. You start all asynchronous tasks at the same time. You only use await on tasks when you need their results. For example, the above code is similar to code in a web application that requests different microservices and then summarizes the results on a single page. You execute all requests immediately, but then you wait for all of these tasks and put the website together.

Combination with tasks

You have everything you need for breakfast ready at the same time, with the exception of the toast. The preparation of the toast is a combination of an asynchronous process (toasting the bread) and synchronous processes (spreading with butter and jam). Updating this code illustrates an important concept:

Important

The combination of an asynchronous operation followed by a synchronous operation results in an asynchronous operation. In other words, if part of an operation is asynchronous, the entire operation is asynchronous.

The above code shows that you can use Task or Task objects to keep tasks in progress. You wait with "" for each task before using its result. The next step is to create methods that combine other activities. Before serving breakfast, you might want to wait for the task that stands for toasting the bread before coating the toasted bread with butter and jam. You can represent this with the following code:

The above method has the modifier in its signature. This signals to the compiler that this method contains an instruction. So it contains asynchronous operations. This method represents the task of toasting the bread and then brushing it with butter and jam. This method outputs the result Task , i. H. the combination of these three operations. The main code block now looks like this:

The above change illustrates an important technique for working with asynchronous code. You create tasks by breaking the tasks down into a new method that returns a task. You can decide when to wait for this task. You can start other tasks at the same time.

Asynchronous exceptions

Up to this point, you have implicitly assumed that all of these tasks completed successfully. Asynchronous methods, like their synchronous counterparts, throw exceptions. Asynchronous exception and error handling support generally has the same goals as asynchronous support: you should write code that reads like a series of synchronous instructions. Tasks throw exceptions when they cannot complete successfully. The client code can catch these exceptions when a started task is in the state. For example, suppose the toaster catches fire while the toast is being toasted. You can simulate this by changing the method to match the following code:

Note

You will receive a warning if you compile the preceding code in connection with unreachable code. This is by design because operations cannot continue normally once the toaster catches fire.

After making these changes, run the application. The output looks like the following text:

Note that there are a few tasks that will complete between the toaster lighting and the exception being detected. If an asynchronous task throws an exception, that task is faulty. The task object contains the exception that is thrown in the Task.Exception property. Faulty tasks throw an exception when they are expected.

There are two important mechanisms that you need to understand: How do you store an exception in a failed task? How do you unpack and re-throw an exception when code is expecting a bad task?

When code that runs asynchronously throws an exception, that exception is stored in the. The Task.Exception property is a System.AggregateException class because more than one exception might be thrown during asynchronous operations. Any exceptions that are thrown are added to the AggregateException.InnerExceptions collection. If this property is NULL, a new class is created and the exception thrown is the first item in the collection.

The most common scenario for a malfunctioning task is that the property contains exactly one exception. When code expects a malfunctioning task (), the first exception in the AggregateException.InnerExceptions collection is thrown again. For this reason, the output of this example shows a class instead of a class. Extracting the first inner exception makes working with asynchronous methods similar to working with their synchronous counterparts. You can check the property in your code if the scenario might generate multiple exceptions.

Comment out these two lines in the method before proceeding. You want to prevent another fire:

Wait efficiently for tasks

The set of statements at the end of the code above can be improved using the methods of the class. One of these APIs is WhenAll, it returns a task that will complete when all of the tasks in its argument list are complete. This is shown by the following code:

Another option is to use WhenAny. This will return one that will complete when any of its arguments complete. You can “await” the returned task, knowing that it has already been completed. The following code shows how you can use WhenAny to await the first task and then process its result. After processing the result of the completed task, you can remove this completed task from the list of tasks submitted to.

After all of these changes, the final code looks like this:

The final version of the asynchronously cooked breakfast took about 15 minutes because some tasks were running at the same time and the code could monitor several tasks at the same time and only needed to intervene when necessary.

This last code is asynchronous. It reflects more precisely how a person would prepare breakfast. Compare the code above with the first code example in this article. The core actions are still clearly visible when reading the code. You can read this code in the same way that you read the breakfast preparation instructions at the beginning of this article. The language features for and provide the translation each person does to follow these written instructions: start tasks as soon as you can, and don't block progress by waiting for tasks to complete.

Next Steps