In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article will explain in detail how to use iterators to wait for tasks in C#. The content of the article is of high quality, so the editor shares it for you as a reference. I hope you will have some understanding of the relevant knowledge after reading this article.
You may have read about async and await keywords in VS2010 5 and how they help simplify asynchronous programming. Unfortunately, just two years after upgrading VS2010, you are still not ready to upgrade to VS2012. You may think, "if I can write methods that look synchronous in VS2010, but execute them asynchronously. My code will be clearer."
We must admit that async and await are very good syntax sweets, and our approach needs to write more "AsyncResultcallback" methods to adapt to this change. And when you finally upgrade to VS2012 (or later), it will be a trivial matter, replacing this method with the C # keyword, with a simple syntax change, not a painstaking structural rewrite.
Summary
Async/await is a keyword based on asynchronous task mode. Since there is already a very complete documentation description here, I will not elaborate on it here. But it must be pointed out that TAP is extremely handsome! With it you can create a large number of small unit tasks (tasks) that will be completed at some point in the future; tasks can start other (nested) tasks and / or create follow-up tasks that will only be started after the current task is completed. Antecedents and subsequent tasks can be linked to an one-to-many or many-to-one relationship. When the embedded task is completed, the parent task does not need to be associated with the thread (heavyweight resource! ) bind to each other. When performing tasks, you no longer have to worry about the timing of threads, just make a few tips, and the framework will automatically handle these things for you. When the program starts to run, all tasks will go to their own destination like a stream into the sea, and bounce back and interact with each other like Pak Ching's little iron ball.
However, we don't have async and await in Craft 4, but what's missing is just a little bit of the new features of Net5, which we can either sidestep or build ourselves, and the key Task types are still available.
In a clocked 5 async method, you have to wait for a Task. This does not cause the thread to wait; rather, the method returns a Task to its caller, and the Task can wait (if it itself is asynchronous) or attach subsequent parts. (it can also call Wait () in a task or its result, but this is coupled to a thread, so avoid doing that. When the waiting task completes successfully, your asynchronous method continues to run where it is interrupted.
As you may know, the Category 5 compiler overrides its asynchronous methods to be a generated nested class that implements the state machine. C # happens to have one more feature (starting with 2. 0): iterators (yield return way). The approach here is to use an iterator method to build a state machine in Clover 4 and return the Task of a series of waiting steps during the entire process. We can write a method to receive an enumeration of the tasks returned from the iterator, return an overloaded Task to represent the completion of the entire sequence and provide its final result, if any.
Ultimate goal
Stephen Covey suggests that we have priorities. That's what we're doing now. There are plenty of examples of how to use async/await to implement SLAMs (synchronous-looking asynchronous methods). So how do we implement this function without using these keywords. Let's do an example of Category 5 async and see how it can be implemented in Cure 4. Then let's discuss the general ways to transform this code.
The following example shows a way to write the asynchronous read-write method Stream.CopyToAsync () in client 5. Suppose this method is not implemented in .NET 5.
Public static async Task CopyToAsync (this Stream input, Stream output, CancellationToken cancellationToken = default (CancellationToken)) {byte [] buffer = new byte [0x1000]; / / 4 KiB while (true) {cancellationToken.ThrowIfCancellationRequested (); int bytesRead = await input.ReadAsync (buffer, 0, buffer.Length); if (bytesRead = = 0) break; cancellationToken.ThrowIfCancellationRequested (); await output.WriteAsync (buffer, 0, bytesRead);}}
For Clover 4, we will split it into two pieces: one is a method with the same access capability, and the other is a private method with the same parameters but different return types. Private methods implement the same processing with iterations, resulting in a series of waiting tasks (IEnumerable). The actual tasks in the sequence can be non-generics or any combination of different types of generics. (fortunately, generic Task types are subtypes of non-generic Task types)
The same access capability (common) method returns the same type as the corresponding async method: void,Task, or generic Task. It will use extension methods to call the private iterator and convert it to Task or Task.
Public static / * async*/ Task CopyToAsync (this Stream input, Stream output, CancellationToken cancellationToken = default (CancellationToken)) {return CopyToAsyncTasks (input, output, cancellationToken). ToTask ();} private static IEnumerable CopyToAsyncTasks (Stream input, Stream output, CancellationToken cancellationToken) {byte [] buffer = new byte [0x1000]; / / 4 KiB while (true) {cancellationToken.ThrowIfCancellationRequested (); var bytesReadTask = input.ReadAsync (buffer, 0, buffer.Length) Yield return bytesReadTask; if (bytesReadTask.Result = = 0) break; cancellationToken.ThrowIfCancellationRequested (); yield return output.WriteAsync (buffer, 0, bytesReadTask.Result);}}
Asynchronous methods are usually named after "Async" (unless it is an event handler such as startButton_Click). Give the iterator the same name followed by "Tasks" (such as startButton_ClickTasks). If the asynchronous method returns a value of void, it still calls ToTask () but does not return Task. If the asynchronous method returns Task, it calls the generic ToTask () extension method. For the three return types, the asynchronous alternative is as follows:
Public / * async*/ void DoSomethingAsync () {DoSomethingAsyncTasks () .ToTask ();} public / * async*/ Task DoSomethingAsync () {return DoSomethingAsyncTasks () .ToTask ();} public / * async*/ Task DoSomethingAsync () {return DoSomethingAsyncTasks () .ToTask ();}
Paired iterator methods are not more complex. When an asynchronous method waits for a non-generic Task, the iterator simply transfers control to it. When an asynchronous method waits for the task result, the iterator saves the task in a variable, goes to that method, and then uses its return value. Both cases are shown in the CopyToAsyncTasks () example above.
For SLAM containing generic resultTask, the iterator must transfer control to the exact type. ToTask () converts the final task to that type in order to extract its results. Often your iterator will calculate the result value from the intermediate task and only need to package it in the Task. .net 5 provides a convenient static method for this. Net 4 does not, so we use TaskEx.FromResult (value) to implement it.
The last thing you need to know is how to handle the values returned in the middle. An asynchronous method can be returned from multiple nested blocks; our iterator simply mimics it by jumping to the end.
/ / Centr5 public async Task DoSomethingAsync () {while (…) {foreach (...) {return "Result";} / / Crun4; DoSomethingAsync () is necessary but omitted here. Private IEnumerable DoSomethingAsyncTasks () {while (…) {foreach (...) {yield return TaskEx.FromResult ("Result"); goto END;}} END:;}
Now we know how to write SLAM in FromResult 4, but only if we implement the FromResult () and two ToTask () extension methods. Let's get started.
A simple beginning
We will implement three methods under the class System.Threading.Tasks.TaskEx, starting with the simple two methods. The FromResult () method creates a TaskCompletionSource (), then assigns a value to its result, and finally returns Task.
Public static Task FromResult (TResult resultValue) {var completionSource = new TaskCompletionSource (); completionSource.SetResult (resultValue); return completionSource.Task;}
Obviously, the two ToTask () methods are basically the same, the only difference is whether to assign a value to the Result property of the returned object Task. Usually we don't write two pieces of the same code, so we use one of these methods to implement the other. We often use generics as the return result set so that we don't have to worry about the return value and avoid type conversion at the end. Next, let's implement the method that does not use generics.
Private abstract class VoidResult {} public static Task ToTask (this IEnumerable tasks) {return ToTask (tasks);}
So far we have only one ToTask () method that hasn't been implemented yet.
The first naive attempt
For the method we tried to implement for the first time, we will enumerate the Wait () of each task to complete, and then take the final task as the result (if appropriate). Of course, we don't want to occupy the current thread, we will loop the task with another thread.
/ / BAD CODE! Public static Task ToTask (this IEnumerable tasks) {var tcs = new TaskCompletionSource (); Task.Factory.StartNew () = > {Task last = null; try {foreach (var task in tasks) {last = task; task.Wait ();} / / Set the result from the last task returned, unless no result is requested. Tcs.SetResult (last = = null | | typeof (TResult) = = typeof (VoidResult)? Default (TResult): ((Task) last) .result);} catch (AggregateException aggrEx) {/ / If task.Wait () threw an exception it will be wrapped in an Aggregate; unwrap it. If (aggrEx.InnerExceptions.Count! = 1) tcs.SetException (aggrEx); else if (aggrEx.InnerException is OperationCanceledException) tcs.SetCanceled (); else tcs.SetException (aggrEx.InnerException);} catch (OperationCanceledException cancEx) {tcs.SetCanceled ();} catch (Exception ex) {tcs.SetException (ex);}}; return tcs.Task;}
Here are some good things that actually work, as long as you don't touch the user interface:
It accurately returns a Task of TaskCompletionSource and sets the completion status through the source code.
1. It shows how we can set the final Result of task through the last task of the iterator while avoiding situations where there may be no results.
two。 It catches exceptions from the iterator and sets the Canceled or Faulted state. It also propagates the task state of the enumeration (here through Wait (), which may throw a collection of exceptions wrapped in cancellation or fault).
But there are some major problems. The most serious thing is:
1. Because the iterator needs to fulfill the promise of "asynchronous", when it is initialized from a UI thread, the iterator's methods will be able to access the UI control. You can see that the foreach loops here are all running in the background; don't touch UI from that moment on! This approach does not take into account SynchronizationContext.
two。 We also have problems outside of UI. We may want to make a large number of parallel running Tasks implemented by SLAM. But look at the Wait () in the loop! When waiting for a nested task, it may take a long time for the remote to complete, and we will suspend a thread. We are faced with a situation in which thread resources in the thread pool are exhausted.
3. This method of unpacking Aggregate exceptions is unnatural. We need to capture and propagate its completion status without throwing an exception.
4. Sometimes SLAM can determine its completion status immediately. In that case, the async of Category 5 can operate asynchronously and efficiently. We always plan a background task here, so we lose that possibility.
It's time to do something!
Continuous cycle
The biggest idea is to get the first task it produces directly from the iterator. We create a continuation so that it can check the status of the task when it is completed and (if successful) receive the next task and create another continuation until it ends. If not, that is, the iterator has no requirements to complete. )
/ / very impressive, but we haven't yet. Public static Task ToTask (this IEnumerable tasks) {var taskScheduler = SynchronizationContext.Current = = null? TaskScheduler.Default: TaskScheduler.FromCurrentSynchronizationContext (); var tcs = new TaskCompletionSource (); var taskEnumerator = tasks.GetEnumerator (); if (! taskEnumerator.MoveNext ()) {tcs.SetResult (default (TResult)); return tcs.Task;} taskEnumerator.Current.ContinueWith (t = > ToTaskDoOneStep (taskEnumerator, taskScheduler, tcs, t), taskScheduler); return tcs.Task } private static void ToTaskDoOneStep (IEnumerator taskEnumerator, TaskScheduler taskScheduler, TaskCompletionSource tcs, Task completedTask) {var status = completedTask.Status; if (status = = TaskStatus.Canceled) {tcs.SetCanceled ();} else if (status = = TaskStatus.Faulted) {tcs.SetException (completedTask.Exception);} else if (! taskEnumerator.MoveNext ()) {/ / sets the result returned by the last task until no result is needed. Tcs.SetResult (typeof (TResult) = = typeof (VoidResult)? Default (TResult): ((Task) completedTask) .result);} else {taskEnumerator.Current.ContinueWith (t = > ToTaskDoOneStep (taskEnumerator, taskScheduler, tcs, t), taskScheduler);}}
There's a lot to share here:
1. Our subsequent section (continuations) uses TaskScheduler that involves SynchronizationContext, if any. This allows our iterator to be called immediately or at a continuation point after the UI thread is initialized to access the UI control.
two。 The process runs without interruption, so there are no threads hanging and waiting! By the way, the call to itself in ToTaskDoOneStep () is not a recursive call; it is an anonymous function called after the end of taskEnumerator.Currenttask, and the current activity exits almost immediately when calling ContinueWith (), which is completely independent of the subsequent parts.
3. In addition, we verify the status of each nested task in the continuation point, rather than checking a predictive value.
However, there is at least one big problem and some smaller ones.
1. If the iterator throws an unhandled exception or cancels by throwing an OperationCanceledException, we do not handle it or set the state of the main task. This is what we have done before, but it is lost in this version.
two。 To fix problem 1, we have to introduce the same exception handling mechanism where MoveNext () is called in both methods. Even now, the same subsequent parts are established in both methods. We violated the creed of "Don't repeat yourself."
3. What if the asynchronous method is expected to give a result, but the iterator does not provide it and then exits? Or is its last task the wrong type? In the first case, we silently return to the default result type; in the second case, we throw an unprocessed InvalidCastException, and the main task never reaches the end state! Our program will hang forever.
4. Finally, what if a nested task cancels or an error occurs? We set the main task state and never call the iterator again. It could be inside a using block, or a try block with finally, and there is some cleanup to do. We should follow the process to end it when it is interrupted, rather than waiting for the garbage collector to do this. How do we do that? Through a follow-up section, of course!
To solve these problems, we remove the MoveNext () call from ToTask () and replace it with a synchronous call to the initialization of ToTaskDoOneStep (). Then we will add appropriate exception handling in a precaution.
Final version
Here is the final implementation of ToTask (). It returns the main task with a TaskCompletionSource, which never causes the thread to wait, if any, SynchronizationContext is involved, the iterator handles the exception, propagates the end of the nested task directly (instead of AggregateException), returns a value to the main task when appropriate, and reports an error with a friendly exception when a result is expected and the SLAM iterator does not end with the correct genericTask type.
Public static Task ToTask (this IEnumerable tasks) {var taskScheduler = SynchronizationContext.Current = = null? TaskScheduler.Default: TaskScheduler.FromCurrentSynchronizationContext (); var taskEnumerator = tasks.GetEnumerator (); var completionSource = new TaskCompletionSource (); / / Clean up the enumerator when the task completes. CompletionSource.Task.ContinueWith (t = > taskEnumerator.Dispose (), taskScheduler); ToTaskDoOneStep (taskEnumerator, taskScheduler, completionSource, null); return completionSource.Task;} private static void ToTaskDoOneStep (IEnumerator taskEnumerator, TaskScheduler taskScheduler, TaskCompletionSource completionSource, Task completedTask) {/ / Check status of previous nested task (if any), and stop if Canceled or Faulted. TaskStatus status; if (completedTask = = null) {/ / This is the first task from the iterator; skip status check. } else if ((status = completedTask.Status) = = TaskStatus.Canceled) {completionSource.SetCanceled (); return;} else if (status = = TaskStatus.Faulted) {completionSource.SetException (completedTask.Exception); return;} / / Find the next Task in the iterator; handle cancellation and other exceptions. Boolean haveMore; try {haveMore = taskEnumerator.MoveNext ();} catch (OperationCanceledException cancExc) {completionSource.SetCanceled (); return;} catch (Exception exc) {completionSource.SetException (exc); return;} if (! haveMore) {/ / No more tasks; set the result (if any) from the last completed task (if any). / / We know it's not Canceled or Faulted because we checked at the start of this method. If (typeof (TResult) = = typeof (VoidResult)) {/ / No result completionSource.SetResult (default (TResult)) } else if (! (completedTask is Task)) {/ / Wrong result completionSource.SetException (new InvalidOperationException ("Asynchronous iterator" + taskEnumerator + "requires a final result task of type" + typeof (Task). FullName + (completedTask = = null? ", but none was provided.": " The actual task type was "+ completedTask.GetType () .FullName));} else {completionSource.SetResult (Task) completedTask) .result);}} else {/ / When the nested task completes, continue by performing this function again. TaskEnumerator.Current.ContinueWith (nextTask = > ToTaskDoOneStep (taskEnumerator, taskScheduler, completionSource, nextTask), taskScheduler);}}
Look at that! Now you will write SLAMs in Visual Studio 2010 with Clip4 (or VB10) without async and await (seemingly synchronous method, but asynchronous execution).
So much for the operation of using iterators to wait for tasks in C#. I hope the above content can be helpful to you and learn more knowledge. If you think the article is good, you can share it for more people to see.
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.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.