Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize Asynchronous programming in C #

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/02 Report--

In this issue, the editor will bring you about how to achieve asynchronous programming in C#. The article is rich in content and analyzes and narrates it from a professional point of view. I hope you can get something after reading this article.

Before Await

In an asynchronous method decorated with async, if you don't encounter await, your code will be on the calling thread all the time. In UI applications, such as ASP.NET or WinForm programs, your code runs on the ASP.NET worker thread or the WinForm worker thread.

Let's take a look at the following example

1: public async Task GetResultAsync () 2: {3: Console.WriteLine (); 4: 5: User user = this.GetUserAsync (); 6: 7: / / call other code 8: 9: return Task.CompletedTask; 10:}

In the above example, we call another asynchronous method in one asynchronous method, but we do not use await, this code is still executed on the original calling thread, and this method only plays the role of propagating asynchronism.

When we do this on the UI thread, the code is executed on the UI thread, and the page is unresponsive until the execution ends. So if the page does not respond for a long time, it may not be caused by asynchronism, there may be other reasons, which need to be taken into account, and you can use a performance analyzer to see what affects the system.

In Await

After the code arrives at await, which thread is performing the asynchronous operation?

Let's take ASP.NET as an example. For operations such as network requests, there are no threads performing asynchronous operations at this time. They are all blocked and waiting for the operation to complete. But if Task.Run is used, threads in the thread pool will be used to perform the task.

So the problem is, when we write an asynchronous method, we can really see that the method is executed, and there must be a thread to execute it.

Yes, it does require a thread to execute, which we call the IO completion port thread. This thread waits for the network request to complete, while it is shared among all network requests. When the network request is completed, the interrupt handler in the operating system is added to the queue of the IO completion port in Job. After the request is initiated, and before the response is returned, they need to be processed by a single IO completion port in turn.

In fact, there are generally only a small number of IO completion port threads to take full advantage of multiple CPU cores. It is important to note that no matter how many requests there are currently, the number of threads is fixed.

Refer to the following operation diagram

SynchronizationContext

In my article on Asynchronous programming (1), I talked about the SynchronizationContext class, which is a class provided by the .NET Framework that can run code in specific types of threads.

.net uses a variety of SynchronizationContext, the most common being the UI thread context used by ASP.NET, WinForms, and WPF. There is nothing special about the instance of SynchronizationContext itself, but it points to its subclass and has static members that can be used to read and control the current SynchronizationContext.

The current SynchronizationContext is a property of the current thread. Anywhere a particular thread runs, you can get the current SynchronizationContext and store it, and you can use SynchronizationContext to run code on that particular thread you start. To sum up, we don't need to know which thread the code starts on, just use SynchronizationContext and we can go back to the startup thread.

An important method of SynchronizationContext is POST, which makes the delegate run in the correct context.

Some SynchronizationContext encapsulate a single thread, such as a UI thread. Some threads encapsulate a specific type of thread, such as a thread pool, but you can choose to send a delegate to any of these threads. Some do not change the thread on which the code is running, but are only used for monitoring, such as ASP.NET SynchronizationContext.

To this place, we need to understand a problem. Before await, our code was running on the calling thread, so after await, which thread did it go to to restore the method?

In fact, in most cases, the code after await is also run by the calling thread, although the calling thread may have done something else while waiting. C # uses SynchronizationContext to do this. When waiting for the task to complete, the current synchronization context is stored as part of the pause method. Then, when the method is restored, the infrastructure of the await keyword uses POST to restore the method on the captured synchronization context.

Since there are most cases, there must be niche cases, and the following situations can be run on different threads

SynchronizationContext has multiple threads, such as thread pool

SynchronizationContext is not really switching the context of a thread

When waiting is reached, there is no current synchronization context, such as in a console application.

Configure the task to recover without using the synchronization context

Note:

For UI applications, recovering on the same thread is the most important thing, and we wait for the safe operation of UI.

Parse asynchronous operation

Take WinForm as an example, we design a button to download our favorite small icons. After the user clicks the button, the UI thread starts and performs the response operation. The following picture shows the flow of an asynchronous operation and how the UI thread switches from the IO thread during that time.

1. When the user clicks the button, the event handler GetButton_OnClick starts queuing to run.

2. The user interface thread executes the first half of the GetButton_OnClick, including the call to GetFaviconAsync.

3. The UI thread continues to enter the GetFaviconAsync and executes the first half of it, including the call to DownloadDataTaskAsync.

4. The UI thread continues to enter the DownloadDataTaskAsync, which starts the download and returns to the task.

5. The UI thread leaves DownloadDataTaskAsync and returns await at GgetFaviconAsync.

6. The current UI thread has captured the SynchronizationContext.

7. GetFaviconAsyncy will wait because it has the identity of await. When the DownloadDataTaskAsync is completed, GetFaviconAsyncy will use the captured SynchronizationContext to recover.

8. The user thread leaves GetFaviconAsync, returns a task, and runs to await in GetButton_OnClick.

9. Similarly, GetButton_OnClick is waiting to be paused.

10. The user thread leaves the GetButton_OnClick and may be used to handle other operations. [at this time, we are waiting for the icon to download. It might take a few seconds. Note that the UI thread is free to handle other user operations, while the IO completion port thread is not involved. The total number of threads blocked during the operation is zero. ]

11. The download is complete, so the IO completion port queues the logic in DownloadDataTaskAsync.

12. The IO completion port thread will set the task returned by DownloadDataTaskAsync to complete.

The IO completion port thread runs the code inside the task and processes the completion, and calls POST on the captured synchronization context (UI thread) to continue running the next code.

14. The IO completion port thread is released and may work on other IO.

15. The user interface thread finds the POST instruction and continues to execute the second half of the GetFaviconAsync until it is finished.

16. When the UI thread leaves GetFaviconAsync, it sets the task returned by GetFaviconAsync to complete.

17. At this runtime, the current synchronization context is the same as the captured context, so the synchronization continues without the need for POST,UI threads. [this logic is not valid in WPF because WPF often creates new SynchronizationContext objects. Although they are equivalent, this makes TPL think that it needs to re-POST. ]

The user thread continues to run the second half of the GetButton_OnClick until it is finished.

Summary

Each implementation of the synchronization context executes POST in a different way, which is a very performance-consuming thing. To avoid this overhead, .NET also has its own optimization mechanism that does not use POST when the captured SynchronizationContext is the same as the current context when the task is completed. Interestingly, if you use the debugger to look at this situation, you will find that the call stack is upside down.

However, this requires system overhead when the synchronization context is different. In performance-critical code or in a code base, if we don't care which thread is used, we can also avoid this overhead by doing it manually.

Call ConfigureaWait to finish before waiting for the task. This does not revert to the original synchronization context.

Byte [] bytes = await httpClient.PostAsJsonAsync (url,data) .ConfigureAwait (false) .ReadAsStreamAsync ()

However, ConfigureAwait is not a strict instruction, it is an identity of the .NET design to tell the runtime that we don't mind which thread the method runs on. If the thread is not important (thread pool thread), it will continue to execute the code. If it is an important thread, .NET will release the thread through its own mechanism and let it do something else, and the method will be restored in the thread pool. Net uses the thread's current SynchronizationContext to determine whether it is important or not.

As mentioned earlier, this article once again mentions that running asynchronous code in synchronous code may have hidden problems. Task has a Result property that prevents waiting for the task to complete. Such as the following code:

1: var result = GetUserAsync () .Result

However, deadlocks can occur if SynchronizationContext is used in a single thread, such as a UI thread. The solution to the problem is that we can use thread pool threads to solve the problem. Such as the following code:

1: var result = Task.Run (() = > GetUserAsync ()) .result. This is how asynchronous programming is implemented in C# shared by the editor. If you happen to have similar doubts, please refer to the above analysis to understand. If you want to know more about it, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report