Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Initiating the Asynchronous Operation



An asynchronous method based on the TAP is permitted to do a small amount of work synchronously before it returns the resulting Task. The work should be kept to the minimal amount necessary, performing operations such as validating arguments and initiating the asynchronous operation. It is likely that asynchronous methods will be invoked from user interface threads, and thus any long-running work in the synchronous up-front portion of an asynchronous method could harm responsiveness. It is also likely that multiple asynchronous methods will be launched concurrently, and thus any long-running work in the synchronous up-front portion of an asynchronous method could delay the initiation of other asynchronous operations, thereby decreasing benefits of concurrency.

In some cases, the amount of work required to complete the operation is less than the amount of work it would take to launch the operation asynchronously (e.g. reading from a stream where the read can be satisfied by data already buffered in memory). In such cases, the operation may complete synchronously, returning a Task that has already been completed.

Exceptions

An asynchronous method should only directly raise an exception to be thrown out of the MethodNameAsync call in response to a usage error*. For all other errors, exceptions occurring during the execution of an asynchronous method should be assigned to the returned Task. This is the case even if the asynchronous method happens to complete synchronously before the Task is returned. Typically, a Task will contain at most one exception. However, for cases where a Task is used to represent multiple operations (e.g. Task.WhenAll), multiple exceptions may be associated with a single Task.

(*Per .NET design guidelines, a usage error is something that can be avoided by changing the code that calls the method. For example, if an error state results when a null is passed as one of the method’s arguments, an error condition usually represented by an ArgumentNullException, the calling code can be modified by the developer to ensure that null is never passed. In other words, the developer can and should ensure that usage errors never occur in production code.)

Target Environment

It is up to the TAP method’s implementation to determine where asynchronous execution occurs. The developer of the TAP method may choose to execute the workload on the ThreadPool, may choose to implement it using asynchronous I/O and thus without being bound to a thread for the majority of the operation’s execution, may choose to run on a specific thread as need-be, such as the UI thread, or any number of other potential contexts. It may even be the case that a TAP method has no execution to perform, returning a Task that simply represents the occurrence of a condition elsewhere in the system (e.g. a Task<TData> that represents TData arriving at a queued data structure).

The caller of the TAP method may block waiting for the TAP method to complete (by synchronously waiting on the resulting Task), or may utilize a continuation to execute additional code when the asynchronous operation completes. The creator of the continuation has control over where that continuation code executes. These continuations may be created either explicitly through methods on the Task class (e.g. ContinueWith) or implicitly using language support built on top of continuations (e.g. “await” in C#, “Await” in Visual Basic, “AwaitValue” in F#).

Task Status

The Task class provides a life cycle for asynchronous operations, and that cycle is represented by the TaskStatus enumeration. In order to support corner cases of types deriving from Task and Task<TResult> as well as the separation of construction from scheduling, the Task class exposes a Start method. Tasks created by its public constructors are referred to as “cold” tasks, in that they begin their life cycle in the non-scheduled TaskStatus.Created state, and it’s not until Start is called on these instances that they progress to being scheduled. All other tasks begin their life cycle in a “hot” state, meaning that the asynchronous operations they represent have already been initiated and their TaskStatus is an enumeration value other than Created.

All tasks returned from TAP methods must be “hot.” If a TAP method internally uses a Task’s constructor to instantiate the task to be returned, the TAP method must call Start on the Task object prior to returning it. Consumers of a TAP method may safely assume that the returned task is “hot,” and should not attempt to call Start on any Task returned from a TAP method. Calling Start on a “hot” task will result in an InvalidOperationException (this check is handled automatically by the Task class).

Optional: Cancellation

Cancellation in the TAP is opt-in for both asynchronous method implementers and asynchronous method consumers. If an operation is built to be cancelable, it will expose an overload of the MethodNameAsync method that accepts a System.Threading.CancellationToken. The asynchronous operation will monitor this token for cancellation requests, and if a cancellation request is received, may choose to honor that request and cancel the operation. If the cancellation request is honored such that work is ended prematurely, the Task returned from the TAP method will end in the TaskStatus.Canceled state.

To expose a cancelable asynchronous operation, a TAP implementation provides an overload that accepts a CancellationToken after the synchronous counterpart method’s parameters. By convention, the parameter should be named “cancellationToken”.

public Task<int> ReadAsync(

byte [] buffer, int offset, int count,

CancellationToken cancellationToken);

 

If the token has cancellation requested and the asynchronous operation is able to respect that request, the returned task will end in the TaskStatus.Canceled state; there will be no available Result and no Exception. The Canceled state is considered to be a final, or completed, state for a task, along with the Faulted and RanToCompletion states. Thus, a task in the Canceled state will have its IsCompleted property returning true. When a task completes in the Canceled state, any continuations registered with the task will be scheduled or executed, unless such continuations opted out at the time they were created, through use of specific TaskContinuationOptions (e.g. TaskContinuationOptions.NotOnCanceled). Any code asynchronously waiting for a canceled task through use of language features will continue execution and receive an OperationCanceledException (or a type derived from it). Any code blocked synchronously waiting on the task (through methods like Wait or WaitAll) will similarly continue execution with an exception.

If a CancellationToken has cancellation requested prior to the invocation of a TAP method that accepts that token, the TAP method should return a Canceled task. However, if cancellation is requested during the asynchronous operation’s execution, the asynchronous operation need not respect the cancellation request. Only if the operation completes due to the cancellation request should the returned Task end in the Canceled state; if cancellation is requested but a result or an exception is still produced, the Task should end in the RanToCompletion or Faulted state, respectively.

For methods that desire having cancellation first and foremost in the mind of a developer using the asynchronous method, an overload need not be provided that doesn’t accept a CancellationToken. For methods that are not cancelable, overloads accepting CancellationToken should not be provided; this helps indicate to the caller whether the target method is actually cancelable. A consumer that does not desire cancellation may call a method that accepts a CancellationToken and provide CancellationToken.None as the argument value; CancellationToken.None is functionally equivalent to default(CancellationToken).

Optional: Progress Reporting

Some asynchronous operations benefit from providing progress notifications; these are typically utilized to update a user interface with information about the progress of the asynchronous operation.

In the TAP, progress is handled through an IProgress<T> interface (described later in this document) passed into the asynchronous method as a parameter named “progress”. Providing the progress interface at the time of the asynchronous method’s invocation helps to eliminate race conditions that result from incorrect usage where event handlers incorrectly registered after the invocation of the operation may miss updates. More importantly, it enables varying implementations of progress to be utilized, as determined by the consumer. The consumer may, for example, only care about the latest progress update, or may want to buffer them all, or may simply want to invoke an action for each update, or may want to control whether the invocation is marshaled to a particular thread; all of this may be achieved by utilizing a different implementation of the interface, each of which may be customized to the particular consumer’s need. As with cancellation, TAP implementations should only provide an IProgress<T> parameter if the API supports progress notifications.

For example, if our aforementioned ReadAsync method was able to report intermediate progress in the form of the number of bytes read thus far, the progress callback could be an IProgress<int>:

public Task<int> ReadAsync(

byte [] buffer, int offset, int count,

IProgress<int> progress);

 

If a FindFilesAsync method returned a list of all files that met a particular search pattern, the progress callback could provide an estimation as to the percentage of work completed as well as the current set of partial results. It could do this either with a tuple, e.g.:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(

string pattern,

IProgress<Tuple<double,ReadOnlyCollection<List<FileInfo>>>> progress);

 

or with a data type specific to the API, e.g.:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(

string pattern,

IProgress<FindFilesProgressInfo> progress);

 

In the latter case, the special data type should be suffixed with “ProgressInfo”.

If TAP implementations provide overloads that accept a progress parameter, they must allow the argument to be null, in which case no progress will be reported. TAP implementations should synchronously report the progress to the IProgress<T> object, making it cheap for the async implementation to quickly provide progress, and allowing the consumer of the progress to determine how and where best to handle the information (e.g. the progress instance itself could choose to marshal callbacks and raise events on a captured synchronization context).

IProgress<T> Implementations

A single IProgress<T> implementation, Progress<T>, is provided as part of the .NET Framework 4.5 (more implementations may be provided in the future). The Progress<T> class is declared 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, which is raised every time the asynchronous operation reports a progress update. The ProgressChanged event is raised on whatever SynchronizationContext was captured when the Progress<T> instance was instantiated (if no context was available, a default context is used, targeting the ThreadPool). Handlers may be registered with this event; a single handler may also be provided to the Progress instance’s constructor (this is purely for convenience, and behaves just as would an event handler for the ProgressChanged event). Progress updates are raised asynchronously so as to avoid delaying the asynchronous operation while event handlers are executing.  Another IProgress<T> implementation could choose to apply different semantics.


Поделиться:



Последнее изменение этой страницы: 2019-06-09; Просмотров: 193; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.025 с.)
Главная | Случайная страница | Обратная связь