Definition of a task-based Asynchronous Pattern
naming, parameters, and return types
The initiation and completion of asynchronous operations in TAP (task-based Asynchronous Pattern) is performed in a separate way, so only one method is named. This contrasts with the IAsyncResult mode or the APM (asynchronous Programming model, asynchronous programming models) pattern, which must have a Start method name and an end method name, as well as an event-based (event-based) Asynchronous Pattern ( EAP), they require the method name to be async and require one or more events, the event handle delegate type, and the type derived from the event parameter. The asynchronous method in tap is named with the "Async" suffix followed by the action name (for example, Methodnameasync). The asynchronous method in tap returns a task type or TASK<TRESULT>, and whether a void or TResult type is returned based on the corresponding synchronization method.
For example, consider the following "read" method, which reads a specific amount of data into a specific offset buffer:
public class MyClass
{
public int Read (byte [] buffer, int offset, int count);
}
This method corresponds to the APM version with the following two methods:
public class MyClass
{
Public IAsyncResult BeginRead (byte[] buffer, int offset, int count,asynccallback callback, object state);
public int EndRead (IAsyncResult asyncresult);
}
The corresponding method for the EAP version is this:
public class MyClass
{
public void Readasync (byte[] buffer, int offset, int count);
public event Readcompletedeventhandler readcompleted;
}
public delegate void Readcompletedeventhandler (object sender, Readcompletedeventargs EventArgs);
public class Readcompletedeventargs:asynccompletedeventargs
{
public int Result {
Get
}
}
Tap corresponds to the version of only one of the following methods:
public class MyClass
{
Public task<int> Readasync (byte [] buffer, int offset, int count);
}
The parameters of a basic tap method should be the same as the parameters of the synchronization method, and in the same order. However, the "out" and "ref" parameters do not comply with this rule and should be avoided. Any data returned through out or ref can be used as part of the returned task<tresult> result, and can accommodate multiple values using a tuple or a custom data structure.
Purely committed to the creation, operation, or combination of task methods (the asynchronous purpose of the method is explicit on the method name or on the method by type naming) does not need to follow these naming patterns; These methods are often referred to as "combinations". Examples of this approach include tasks. Whenall and Task.whenany are discussed in more depth later in this document.
Performance
Initializing an asynchronous operation
The tap asynchronous approach allows for a small amount of work to be handled synchronously before the task that returns the results. This work should be kept to the minimum quantity required to perform operations such as validating parameters and initiating an asynchronous operation. It is likely that asynchronous methods will be invoked from the user interface thread, so that the early part of the synchronization of all long-running asynchronous methods may compromise responsiveness. It is very likely that more than one asynchronous method will be started at the same time, so the earlier part of the synchronization of all long-running asynchronous methods may delay the initiation of other asynchronous operations, thereby reducing the benefits of concurrency.
In some cases, the amount of work required to complete the operation is less than the amount required for an asynchronous startup operation (for example, reading data from a stream that can be satisfied by data already buffered in memory). In this case, the operation may complete synchronously, returning a completed task.
Abnormal
An asynchronous method should only catch an exception thrown at a Methodnameasync call to respond to a usage error. For all other errors, exceptions that occur during asynchronous method execution should be assigned to the returned task. This situation occurs when the asynchronous method completes synchronously before the task returns. Generally, a task contains at most one exception. However, for a task to represent multiple operations (such as Task.whenall), a single task also associates multiple exceptions.
"* each. NET design guidelines all point out that a usage error can be avoided by changing the code that invokes the method. For example, when passing NULL as a parameter of a method, the error state occurs, and the error condition is usually represented as ArgumentNullException, and the developer can modify the call code to ensure that null is not passed. In other words, developers can and should ensure that usage errors have never occurred in the production code.
Target Environment
The occurrence of asynchronous execution depends on the implementation of the TAP method. The developer of the tap method may choose to execute the workload on the thread pool or choose to implement it using asynchronous I/O, so that it is not bound to a thread with a large number of operations executing, or optionally run on a specific thread, such as a UI thread, or some other potential context. It may even be the case that the tap method has nothing to do, simply return a task that happens elsewhere in the system (such as Task<tdata> says Tdata arrives at a queued data structure).
Callers of the tap method may also block the completion of waiting for a tap method (either by waiting synchronously on the task of the result), or by using a continuation to execute additional code when the asynchronous operation completes. The continuation creator has control over where the continuation code executes. These continuation codes are created either by a task class (such as ContinueWith) or by using language support to implicitly build on the continuation code (such as "await" in C #).
Task State
The task class provides the lifecycle of an asynchronous operation that is represented by a TaskStatus enumeration. The task class exposes a Start method in order to support cases derived from task and task<tresult> types, and from the build separation of schedules. Tasks created through the public constructor are called "cold" tasks, and in the "cold" task they start the lifecycle in the taskstatus.created state of the non-scheduled. They are not prompted to be scheduled until the start call is made on these instances. All other tasks that begin life cycle in the "hot" state mean that the asynchronous operations they represent have been initialized, and that their taskstatus is an enumeration value other than created.
All tasks returned from the tap method must be "hot". If a task's constructor is used internally by the tap method to instantiate the task to be returned, the tap method must call the Start method on the Task object before returning the task. The consumer of the tap method can safely assume that the returned task is "hot" and should not attempt to raise the start with any task that returns from the tap method. A "hot" task that is raised with start causes InvalidOperationException (the task class automatically handles this check).
Optional: Undo
The undo in tap is optional for both the performer of the asynchronous method and the consumer of the asynchronous method. If an operation is going to be canceled, then it exposes
An overload that accepts the System.Threading.CancellationToken Methodnameasync. The asynchronous operation monitors this token for the revocation request and, if a revocation request is received, optionally process the request and cancel the operation. If processing the request causes the task to end prematurely, the task returned from the tap method ends in the taskstatus.canceled state.
To expose an asynchronous operation that can be canceled, the TAP implementation provides an overload that accepts a cancellationtoken after synchronizing the parameters of the corresponding method. By convention, this parameter is named "CancellationToken".
Public task<int> Readasync (
byte [] buffer, int offset, int count,
CancellationToken CancellationToken);
If token has requested a revocation and the asynchronous operation respects the request, the returned task will end in the taskstatus.canceled state, resulting in no available result and no exception. The canceled state is considered to be the final or complete state of a task accompanying the faulted and rantocompletion states. Therefore, the IsCompleted property of the task for the Canceled state returns True. When a task with a canceled state completes, any continuation that is registered with the task is scheduled or executed unless the continuation is passed through a specific taskcontinuationoptions The usage was canceled when it was created (such as taskcontinuationoptions.notoncanceled). Any code that waits asynchronously for an undo task that is used by the language attribute will continue to execute and receive a operationcanceledexception (or type that derives from the exception). Any code that is blocked by synchronizing on the task (through a wait or WaitAll method) will continue to execute and throw an exception.
If CancellationToken has already issued a cancellation request before accepting the TAP method call from that token, the Tap method must return a task in the canceled state. However, if the undo is requested during the execution of an asynchronous operation, then the asynchronous operation does not need to respect the revocation request. The returned task ends in the canceled state only when the undo request operation completes. If a revocation is requested, but the result or exception is still generated, then the task will end in the Rantocompletion or Faulted state respectively.
First, in the minds of developers using asynchronous methods, those desiring to undo need to provide an overload that accepts CancellationToken variables. Overloads that accept CancellationToken should not be provided for methods that cannot be canceled. This helps to tell the caller whether the target method is actually canceled. Consumers who do not desire to be withdrawn can invoke a method of accepting cancellationtoken to take Cancellationtoken.none as the supplied parameter value. The Cancellationtoken.none function is equivalent to default (CancellationToken).
Optional: progress report
Some asynchronous operations benefit from the progress notifications provided, and generally use these progress notifications to update the UI on the progress of asynchronous operations.
In tap, progress is handled by passing the Iprogress<t> interface to the parameter named "Progress" of the asynchronous method. Providing this progress interface at the time of the asynchronous method call helps to eliminate competitive conditions from the incorrect usage because the event handle is incorrectly registered after this operation may have missed the update. More importantly, it makes the progress of the change achievable, because it is up to the consumer to decide. For example, consumers will only care about the latest progress updates, or they may buffer all updates, or they may just want to invoke an action for each update, or they might want to control whether to invoke marshaling to a particular thread. All of these may be accomplished by using implementations of a different interface, each of which can be customized to a particular consumer requirement. Because of the revocation, if the API supports progress notifications, then the TAP implementation should provide only one iprogress<t> parameter.
For example, if the Readasync method we mentioned above can report intermediate progress in the form of bytes read so far, then the progress callback (callback) can be a iprogress<int>
Public task<int> Readasync (
byte [] buffer, int offset, int count,
iprogress<int> progress);
If the Findfilesasync method returns a list of all files that satisfy a particular search pattern, the progress callback can provide a percentage of the work done and an estimate of the current partial result set. It can also handle tuples such as:
Public task<readonlycollection<fileinfo>> Findfilesasync (
String pattern,
iprogress<tuple<double,readonlycollection<list<fileinfo>>>> progress);
Or use the API specific data types, such as:
Public task<readonlycollection<fileinfo>> Findfilesasync (
String pattern,
iprogress<findfilesprogressinfo> progress);
In the latter case, the special data type is suffix "progressinfo".
If the TAP implementation provides overloads that accept the progress parameter, they must allow the parameter to be null and NULL, and the progress will not be reported. The TAP implementation should report the progress of the iprogress<t> objects synchronously, making it cheaper than asynchronous implementations that quickly provide progress. and allow consumers of progress to decide how and where to best process information (such as the progress instance itself can choose to collect callback functions and raise events on a captured synchronization context).
Iprogreee<t> implementation
As part of the. NET Framework 4.5, progress<t> is a single implementation of the iprogress<t> (more implementations will be provided in the future). The progress<t> statement reads as follows:
public class Progress<t>: iprogress<t>
{
public Progress ();
Public Progress (action<t> handler);
protected virtual void Onreport (T value);
public event eventhandler<t> ProgressChanged;
}
An instance of progress<t> exposes a progresschanged event that is triggered each time an asynchronous operation reports a progress update. When the progress<t> instance is instantiated, the event is triggered on the captured synchronization context (if no context is available, then with the default thread pool context). The handle may be registered with this event, and a separate handle may also be provided to the constructor of the progress instance (this is purely for convenience, just like the event handle of the ProgressChanged event). Progress updates are asynchronous triggers to avoid delaying asynchronous operations for event handle execution. Other iprogress<t> implementations may choose to use different semantics.
How to select an overloaded function provided
With the CancellationToken and iprogress<t> parameters, the TAP implementation defaults to 4 overloaded functions:
Public Task methodnameasync (...);
Public Task methodnameasync (..., CancellationToken cancellationtoken);
Public Task methodnameasync (..., iprogress<t> progress);
Public Task methodnameasync (...,
CancellationToken CancellationToken, iprogress<t> progress);
However, because they do not provide cancellation and progress capabilities, many tap implementations have the shortest overload requirements:
Public Task methodnameasync (...);
If one implementation supports cancellation or progress but not simultaneous support, the TAP implementation can provide 2 overloads:
Public Task methodnameasync (...);
Public Task methodnameasync (..., CancellationToken cancellationtoken);
... or ...
Public Task methodnameasync (...);
Public Task methodnameasync (..., iprogress<t> progress);
If the implementation supports both cancellation and progress, it can provide 4 overloads by default. However, only 2 are valid:
Public Task methodnameasync (...);
Public Task methodnameasync (...,
CancellationToken CancellationToken, iprogress<t> progress);
To get the 2 missing overloads, the developer can pass the Cancellationtoken.none (or Default (CancellationToken)) and/or the CancellationToken parameters to the or pass NULL to the progress parameter.
If you expect each usage of the tap method to use cancellation and/or progress, the overload that does not accept the relevant parameter can be ignored.
If multiple overloads of the Tap method expose optional cancellation and/or progress, then cancellation and/ Or progress overloads should behave like the overloads that support them have passed cancellationtoken.none and null respectively to cancellation and progress.
Implementing a task-based Asynchronous Pattern
Generation Method
Compiler Generation
In the. NET Framework 4.5, the C # compiler implements Tap. Any method labeled with the Async keyword is an asynchronous method, and the compiler uses tap to perform the necessary transformations to implement the method asynchronously. Such a method should return a task or task<tresult> type. In the latter case, the method body should return a TResult, and the compiler will ensure that the task<tresult> is available by return. Similarly, unhandled exceptions in the method body are marshaled to the output task, causing the returned task to end in a faulted state. An exception is if the operationcanceledexception (or derived type) is unhandled, then the returned task ends in a canceled state.
manually generated
Developers can implement tap manually, just like the compiler or better control the implementation of the method. Compilers rely on exposed surface areas from the System.Threading.Tasks namespace (and types supported in System.Runtime.CompilerServices based on System.Threading.Tasks), and are available directly to developers The function. When the TAP method is implemented manually, the developer must ensure that the returned task completes when the asynchronous operation completes.
Mixed Build
The implementation of mixed core logic in compiler-generated implementations is often useful for manually implementing a tap. For example, in order to avoid the exception that the method direct caller produces rather than through task exposure, such as:
Public task<int> MethodAsync (string input)
{
if (input = = null) throw new ArgumentNullException ("input");
return methodasyncinternal (input);
}
Private Async task<int> methodasyncinternal (String input)
{
..//code that uses await
}
Arguments should be changed outside of the compiler-generated asynchronous method, which is useful in another case where a "fast track" optimization can be implemented by returning a cached task.
Work load
Calculation of restricted and I/O constrained asynchronous operations can be achieved by means of the tap method. However, when the tap implementation is exposed from a library, it should be provided only to workloads that contain I/O operations (they can also contain calculations but should not include calculations only). If a method is purely calculated, it should be exposed only through an asynchronous implementation, and the consumer can then choose whether to package the call to the synchronization method as a task and/or to implement parallelism in order to unload the task to another thread.
Calculation Limits
The task class is best for representing compute-intensive operations. By default, in order to provide effective execution, it exploits the. NET thread pool, while also providing a lot of control over when, where, and how to perform asynchronous computations.
There are several ways to generate tasks that are constrained by the calculation.
In. Net 4, the primary method for starting a new compute-constrained task is taskfactory.startnew (), which accepts a delegate that executes asynchronously (typically an action or a func<tresult>). If you provide an action, the returned task represents the asynchronous execution of that delegate. If a func<tresult> is provided, a task<tresult> is returned. There is a startnew () overload that accepts Cancellationtoken,taskcreationoptions, and TaskScheduler, which provides fine-grained control over task scheduling and execution. The factory instance acting on the current dispatcher can act as a static property of the task class, such as Task.Factory.StartNew ().
In. Net 4.5, the task type exposes a static Run method as a shortcut to a StartNew method that can be easily used to initiate a constrained task on the thread pool. Starting with. Net 4.5, this is a much more popular mechanism for starting a task that is constrained by computing. StartNew is used directly when the behavior requires more fine-grained control.
The task type exposes constructors and start methods. These are available if you have to detach the scheduler (as mentioned earlier, the exposed APIs must return only tasks that have been started).
The task type exposes multiple ContinueWith overloads. When another task completes, the method creates a new task that will be scheduled. This overload accepts Cancellationtoken,taskcreationoptions, and TaskScheduler, which provide fine-grained control over task scheduling and execution.
The TaskFactory class provides Continuewhenall and Continuewhenany methods. These methods create a new task that is about to be scheduled when all or any one of the tasks in the supply is completed. With ContinueWith, there is support for scheduling control and execution of tasks.
Consider the asynchronous method for rendering the image below. The task body can obtain cancellation token so that when the rendering occurs, the code may exit prematurely if a revocation request arrives. Also, if a cancellation request occurs before the rendering starts, we can block any rendering.
Public task<bitmap> Renderasync (
ImageData data, CancellationToken CancellationToken)
{
Return Task.run (() =>
{
var bmp = new Bitmap (data. Width, data. Height);
for (int y=0; y<data. Height; y++)
{
Cancellationtoken.throwifcancellationrequested ();
for (int x=0; x<data. Width; X + +)
{
...//render pixel [x,y] into BMP
}
}
return BMP;
}, CancellationToken);
}
If at least one of the following conditions is correct, the calculation of the restricted tasks will end with a canceled state:
Before the task is over to the taskstatus.running State, CancellationToken provides (for example, Startnew,run) the parameters of a creation method that emits a revocation request.
There is a task that has an unhandled operationcanceledexception inside it. The operationcanceledexception contains cancellationtoken passed to the task with the same name as the CancellationToken property, and the CancellationToken has issued a revocation request.
If there is another unhandled exception in the task body, the task ends in a faulted state, and any attempt to wait on the task or the result of accessing it will cause an exception to be thrown.
I/O restrictions
Tasks created with the taskcompletionsource<tresult> type should not be returned directly by all executing threads. Taskcompletionsource<tresult> exposes a task attribute that returns the associated task<tresult> instance. The life cycle of the task is controlled by the methods exposed by the taskcompletionsource<tresult> instance, in other words, these examples include Setresult, setexception, setcanceled, and their tryset* variables.
Consider the need to create a task that will be completed after a specific period of time. For example, when a developer wants to delay an activity for a period of time in a UI scenario, this may make it useful. NET has already provided the ability to invoke a delegate asynchronously after a certain period of time, and we can use the taskcompletionsource<tresult> Put a task on a timer, for example:
public static task<datetimeoffset> Delay (int millisecondstimeout)
{
var TCS = new taskcompletionsource<datetimeoffset> ();
New Timer (Self =>
{
((IDisposable) self). Dispose ();
Tcs. Trysetresult (Datetimeoffset.utcnow);
}). Change (millisecondstimeout,-1);
Return TCS. Task;
}
In. Net 4.5, Task.delay () was born for this purpose. For example, such a method can be used inside of another asynchronous method to implement an asynchronous rotation loop:
public static async Task Poll (
Uri URL,
CancellationToken CancellationToken,
Iprogress<bool> progress)
{
while (true)
{
Await Task.delay (timespan.fromseconds), CancellationToken);
BOOL success = FALSE;
Try
{
await DownloadStringAsync (URL);
Success = true;
}
Catch {/* Ignore errors */}
Progress. (success);
}
}
There are no taskcompletionsource<tresult> copies of the form. However,,task<tresult> is derived from the Task, and therefore the taskcompletionsource<tresult> of generics can be used for those I/O constrained methods. They all take advantage of a bogus TResult source (Boolean is the default choice, and if the developer is concerned about the task<tresult> consumer of the task downward transition, then a private TResult type can be used) to simply return a task. For example, the delay method developed before is to return the current time along the resulting task<datetimeoffset>. If such a result value is unnecessary, then the method can be replaced with the following code (note the change of the return type and the change of the Trysetresult parameter):
public static Task Delay (int millisecondstimeout)
{
var TCS = new taskcompletionsource<bool> ();
New Timer (Self =>
{
((IDisposable) self). Dispose ();
Tcs. Trysetresult (TRUE);
}). Change (millisecondstimeout,-1);
Return TCS. Task;
}
tasks with mixed calculation limits and I/O restrictions
Asynchronous methods are not limited to computational limitations or I/O constrained operations, but can represent a mixture of both. In fact, it is often the case that multiple asynchronous operations of a different nature are grouped together to generate a larger blending operation. For example, consider the previous Renderasync method, which performs a computationally intensive operation based on some input imagedata to render a picture. The ImageData may come from a Web service that we access asynchronously:
Public Async task<bitmap> Downloaddataandrenderimageasync (
CancellationToken CancellationToken)
{
var imagedata = await downloadimagedataasync (cancellationtoken);
return await Renderasync (ImageData, CancellationToken);
}
This example also shows how a single cancellationtoken is threaded through multiple asynchronous operations.