When we deal with some long-term calls, it often causes the interface to stop responding or the IIS thread consumes too much, and this time we need to use asynchronous programming to fix these problems, but it is often easier said than done, it is true that asynchronous programming is a completely different programming idea than synchronous programming. , for developers accustomed to synchronous programming, in the development process more difficult, controllability is not strong is its characteristics.
In the. NET Framework5.0, Microsoft has developed a new language feature for us, allowing us to use asynchronous programming just as close and simple as synchronous programming, and this article will explain some of the limitations of the callback-based moral asynchronous programming model in previous versions of the framework and the new API if you let us simply do the same development tasks.
Why asynchronous
All along, the use of remote resources programming is a confusing problem, different from the "local resources", remote resources access There will always be a lot of unexpected situation, the network environment of unstable machine server failure, can cause many programmers completely uncontrolled problems, So this also requires the programmer to need more to protect the remote resource calls, the management call cancellation, the supermarket, the thread waits and the processing thread does not respond to the situation for a long time. And in. NET we often overlook these challenges, in fact we will have a number of different patterns to deal with asynchronous programming, such as in the processing of IO-intensive operations or high-latency operations without the detection of threads, in most cases we have synchronous and asynchronous two ways to do this thing. The problem is that the current patterns are very prone to confusion and code errors, or developers will abandon and then use blocking to develop.
And in today's. NET, provides a programming experience that is very close to synchronous programming and does not require developers to deal with many situations that only occur in asynchronous programming, and asynchronous invocations are clear and opaque, and are easy to combine with synchronized code.
A bad experience of the past
The best way to understand this problem is in our most common case: The user interface has only one thread all the work is running on this thread, and the client program cannot respond to the user's mouse time, which is probably because the application is being blocked by a time-consuming operation, This may be because the thread is waiting for a network ID or is doing a CPU-intensive calculation, when the user interface does not get run time and the program is in a busy state, which is a very poor user experience.
For many years, the way to solve this problem is to make the asynchronous flower call, do not wait for the response, as soon as possible to return the request, so that other events can be executed at the same time, only when the request has the final feedback when the application is notified that the client code can execute the specified code.
The problem is that asynchronous code completely destroys the code flow, and the callback agent explains how it works, but how does it wait in a while loop? An If statement? A try block or a using block? How to explain "what to do next"?
Look at one of the following examples:
Public intSumpagesizes (ilist<uri>URIs) { intTotal =0; foreach(varUriinchURIs) {Txtstatus.text=string. Format ("Found {0} bytes ...", total); vardata =NewWebClient (). Downloaddata (URI); Total+=data. Length; } Txtstatus.text=string. Format ("Found {0} bytes Total", total); returnTotal ; }
This method downloads files from a URI list, counts their size and updates state information at the same time, obviously this method does not belong to the UI thread because it takes a very long time to complete, so it will completely suspend the UI, but we want the UI to be continuously updated, how to do it?
We can create a background program that continues to send data to the UI thread to let the UI update itself, which seems wasteful because the thread spends most of its time waiting and downloading, but sometimes that's what we need to do. In this example, WebClient provides an asynchronous version of the Downloaddata method-downloaddataasync, which returns immediately and then triggers an event after downloaddatacompleted. This allows the user to write an asynchronous version of the method to split the thing, the call immediately returns and completes the call on the next UI thread, thus no longer blocking the UI thread. Here's the first attempt:
Public voidSumpagesizesasync (ilist<uri>URIs) {Sumpagesizesasynchelper (URIs). GetEnumerator (),0); } Public voidSumpagesizesasynchelper (ienumerator<uri> Enumerator,intTotal ) { if(Enumerator. MoveNext ()) {Txtstatus.text=string. Format ("Found {0} bytes ...", total); varClient =NewWebClient (); Client. Downloaddatacompleted+ = (sender,e) ={sumpagesizesasynchelper (enumerator, total+e.result.length); }; Client. DownloadDataAsync (Enumerator. Current); } Else{Txtstatus.text=string. Format ("Found {0} bytes Total", total); } }
And then it's still bad, we broke a neat foreach loop and got a enumerator manually, and each call created an event callback. The code replaces the loop with recursion, which you should not dare to look directly at. Don't worry, it's not finished yet.
The original code returns a total sum and displays it, and the new step version is returned to the caller before the statistics have been completed. How can we get a result back to the caller, the answer is: The caller must support a fallback, and we can call it after the statistics are complete.
But what about exceptions? The original code did not pay attention to the exception, it was passed on to the caller, and in the asynchronous version we had to extend back to let the exception propagate, and we had to make it explicit when the exception happened.
Ultimately, these needs will further confuse your code:
Public voidSumpagesizesasync (ilist<uri> uris,action<int,exception>callback) {Sumpagesizesasynchelper (URIs). GetEnumerator (),0, callback); } Public voidSumpagesizesasynchelper (ienumerator<uri> Enumerator,inttotal,action<int,exception>callback) { Try { if(Enumerator. MoveNext ()) {Txtstatus.text=string. Format ("Found {0} bytes ...", total); varClient =NewWebClient (); Client. Downloaddatacompleted+ = (sender, e) = ={sumpagesizesasynchelper (enumerator, total+e.result.length,callback); }; Client. DownloadDataAsync (Enumerator. Current); } Else{Txtstatus.text=string. Format ("Found {0} bytes Total", total); Enumerator. Dispose (); Callback (Total,NULL); } } Catch(Exception ex) {enumerator. Dispose (); Callback (0, ex); } }
When you look at the code again, can you immediately tell what it is, a JB thing?
I'm afraid not, we're just trying to replace the blocking call with a synchronous method just by using an asynchronous call, wrap it in a foreach loop, think about trying to assemble more asynchronous calls or have more complex control structures, This is not a subpagesizesasync scale to solve.
Our real problem is that we can no longer explain the logic in these methods, and our code is completely out of the box. Many of the work in async code makes the whole thing look hard to read and seems to be full of bugs.
A new way
Now that we have a new feature to solve the above problem, the async version of the code will look like this:
Public Asynctask<int> Sumpagesizesasync (ilist<uri>URIs) { intTotal =0; foreach(varUriinchURIs) {Txtstatus.text=string. Format ("Found {0} bytes ...", total); vardata =await NewWebClient (). Downloaddatataskasync (URI); Total+=data. Length; } Txtstatus.text=string. Format ("Found {0} bytes Total", total); returnTotal ; }
In addition to the highlighted part, the code above is very similar to the synchronous version of the code, the process of the code has never changed, and we do not see any callbacks, but this does not mean that there is actually no callback operation, the compiler will take care of the work, no longer need you to concern.
The Async method uses task<int> instead of the originally returned int type, and the task and task<t> are provided in today's framework to represent a running job.
Async methods do not have an additional method, according to the Convention in order to distinguish the synchronous version of the method, we add async as the new method name after the method name. The method above is also asynchronous, which means that the method realizes that the compiler is treated differently, allowing some of them to become callbacks, and automatically creating task<int> as the return type.
Explanation of this method: Inside the method, call another asynchronous method Downloaddatataskasync, it quickly returns a variable of type task<byte[]>, it will be activated after the download data is completed, until the data is not complete before , we don't want to do anything, so we use await to wait for the operation to complete.
It appears that the await keyword blocks the thread until the task completes the downloaded data, but instead it flags the callback for the task and returns immediately, which executes the callback when the task is completed.
Tasks
The task and task<t> types already exist in the. NET Framework 4.0, a task represents a time-out activity, which may be a CPU-intensive work or an IO operation that runs in a separate thread, It is also very easy to manually create a task that does not work on a separate thread:
StaticTask Readfileasync (stringFilePath, out byte[] buffer) {Stream Stream=File.Open (FilePath, FileMode.Open); Buffer=New byte[Stream. Length]; varTCS =Newtaskcompletionsource<Double>(); Stream. BeginRead (Buffer,0, buffer. Length, arr = { varLength =Stream. EndRead (arr); Tcs. Setresult (stream. Length); }, NULL); returnTCS. Task; }
Once you have created a TaskCompletionSource object, you can return the task object associated with it, asking the client code to get the final result when the relevant work is completed, when the task does not occupy its own thread.
If the actual task fails, the task can carry an exception from the sample and propagate up, if using await will trigger the client code exception:
Static Async voidReadassignedfile () {byte[] buffer; Try { DoubleLength =awaitReadfileasync ("SomeFileDoNotExisted.txt", outbuffer); } Catch(Exception ex) {Console.WriteLine (ex). Message); } } Statictask<Double> Readfileasync (stringFilePath, out byte[] buffer) {Stream Stream=File.Open (FilePath, FileMode.Open); Buffer=New byte[Stream. Length]; varTCS =Newtaskcompletionsource<Double>(); Stream. BeginRead (Buffer,0, buffer. Length, arr = { Try { varLength =Stream. EndRead (arr); Tcs. Setresult (stream. Length); } Catch(IOException ex) {TCS. SetException (ex); } }, NULL); returnTCS. Task; }
Task-based Asynchronous programming model
As explained above, the Async method should look like-task-based asynchronous Pattern (TAP), the asynchronous embodiment above only requires a calling method and asynchronous async method, which returns a Task or task<t>.
Some of the conventions in tap are described later in this article, including how to handle "Cancel" and "in progress", and we'll explain the task-based programming model further.
Async and await
It is important to understand that the Async method does not run on its own thread, in fact, writing an async method without any await, it will be a no-compromise synchronization method:
Static Async task<int> tentosevenasync () { thread.sleep (10000); return 7 ; }
If you call this method, you will block the thread for 10 seconds and return 7, which may not be what you expect, and you will get a warning in VS, as this may never be the desired result.
Only one async method will return control to the caller as soon as it runs to an await statement, but only after the awaited task is completed does it actually return the result, which means you need to make sure that the code in the Async method does not do too much work or block performance calls. The following example is the effect you expect
Static Async task<int> tentosevenasync () { await task.delay (+); return 7 ;}
Task.delay is actually an asynchronous version of Tread,sleep that returns a task that will be completed within the specified time.
Time handlers and Async methods with no return value
Async methods can be created from other async methods using await, but where does the async end?
In the client program, the usual answer is that the asynchronous method is initiated by the event, and the user taps a button, an asynchronous method is activated until it is complete, and the event itself does not relate to when the method executes. That's what's usually called "forgetting after a hair."
To accommodate this pattern, asynchronous methods are usually explicitly designed to "forget after sending"-using void as the return value instead of the task<tresult> type, which allows the method to act directly as an event handler. When a void Saync method executes, no task is returned and the caller cannot trace whether the call is complete.
Private Async void Somebutton_click (object sender, RoutedEventArgs e) { false; await Sumpagesizesasync (Geturls ())); true ;}
Conclusion
The more you write to the end, the less you talk.
Comprehensive parsing of asynchronous programming in C #