.NET提供了执行异步操作的三种模式:
async
和await
关键词和Await
运算符为TAP添加了语言支持。IAsyncResult
接口提供异步行为的旧模型。在APM模式下,同步操作需要Begin和End方法。不建议新的开发使用此模式。在.NET中,基于任务的异步模式是建议用于新开发的异步设计模式。它是基于System.Threading.Tasks命名中的Task
和Task<TResult>
类型,这个类型用于表示异步操作。
TAP使用单个方法表示异步操作的开始和完成。这与异步编码模型(APM
或IAsyncReult
)模式和基于事件的异步模式(EAP
)形成对比。APM
需要Begin
和End
方法。EAP需要后缀为Async
的方法,以及一个或多个事件,事件处理程序委托类型和EventArg
派生类型。
TAP中的异步方法在返回类型:
System.Threading.Tasks.Task
System.Threading.Tasks.Task<TResult>
具体返回类型取决于相应的同步方法返回的是void还是类型TResult
。
TAP异步方法参数:
TAP方法的参数应与其同步对应方法的参数匹配,并应以相同顺序提供。但是,out
和ref
参数不受此规则的限制,并应完全避免。应该将通过out
或ref
参数返回的所有数据改为作为由TResult
返回的Task<TResult>
的一部分返回,且应使用元组或自定义数据结构来容纳多个值。即使TAP方法的同步对应项没有提供CancellationToken
参数,也应考虑添加此参数。
基于TAP的异步方法可以同步完成少量工作,如在返回结果任务之前,验证自变量和启动异步操作。应将同步工作保持最小,以便异步方法可以快速返回。快速返回的原因包括:
自.NET Framework 4.5起,任何归于async
关键字的方法都被视为异步方法,并且编译器会执行必要的转换,以使用TAP异步实现方法。异步方法应返回System.Treading.Tasks.Task
或System.Threading.Tasks.Task<TResult>
对象。对于后者,函数的主体应该返回TResult
,并且编译器确保此结果是通过生成的任务对象获得。同样,未在方法的主体中处理的任何异常都会被封装处理未输出任务并导致生成的任务结束以TaskStatus.Faulted
状态结束。此规则的异常发生在OperationCanceledException
未得到处理时,在这种情况下生成的任务以TaskStatus.Canceled
状态结束。
手动实现TAP模式以更好的控制实现。编译器依赖从System.Threading.Tasks
命名空间公开的公共外围应用和System.Runtime.CompilerServices
命名空间中支持的类型。手动实现TAP
,需要创建一个TaskCompletionSource<TResult>
对象,执行异步操作,并在操作完成时,调用SetResult
、SetException
、SetCanceled
方法或调用这些方法之一的Try
版本。手动实现TAP
方法时,需在所在表示的异步操作完成生成的任务。例如:
public static Task<int> ReadTask(this Stream stream,byte[] buffer,int offset,int count,object state)
{
var tcs =new TaskCompletionSource<int>();
stream.BeginRead(buffer,offset,count,resp=>{
try
{
tcs.SetResult(stream.EndRead(resp));
}
catch(Exception ex){tcs.SetException(ex);}
},state);
return tcs.Task;
}
混合方法可以将核心逻辑委托给编译器。如果想验证编译器生成的异步方法之外的参数时,可能需要使用这种混合方法,以便异常可以转义到该方法的直接调用方而不是通过System.Threading.Tasks.Task
对象被公开:
public Task<int> MethodAsync(string input)
{
if(input==null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
}
private async Task<int> MethodAsyncInternal(string input)
{
return value;
}
使用await
关键字开异步等待Task
和Task<TResult>
对象。等待Task
时,await
表达式的返回类型为void
。等待Task<TResult>
时,await
表达式的类型为TResult
。await
表达式必须出现在异步方法的正文内。
实际上,await 功能通过使用延续任务在任务上安装回叫。此回叫在挂起点恢复异步方法。 恢复异步方法时,如果等待的操作已成功完成且为Task<TResult>
,返回的是TResult
。如果等待的Task
或Task<TResult>
以Canceked状态结束。就会抛出OpetationCanceledException
异常。如果等待的Task
或Task<TResult>
以Faulted
状态结束,就会抛出导致它发生故障的异常。一个Task
可能由于多个异常而出错,但只会传播一个异常。不过Task.Exception
属性会返回包含所有错误AggregateException
异常。
调用异步方法时,将同步执行函数的正文,直到遇见尚未完成的可等待实例上的第一个 await 表达式,此时调用返回到调用方。如果异步方法不返回void
,将会返回Task
或Task<TResult>
对象,以表示正在进行的计算。在非void异常方法中,如果遇到return语句或到达方法正文末尾,任务就以RanToCompletion
最终状态完成。如果未经处理的异常导致无法控制异步方法正文,任务就以Faulted
状态结束。如果异常为OperationCanceledException
,任务改为以Canceled
状态结束。
使用Task.Yield
方法,将暂停点引入异步方法。
Task.Run(async delegate{
int flag=0;
do{
await Task.Yield();
flag++;
}while(flag<10000);
});
使用Task.ConfigureAwait
方法,更好地控制异步方法中的暂停和恢复。默认情况下,异步方法挂起时会捕获当前上下文,捕获的上下文用于在恢复时调用异步方法的延续。
System.Threading
命名空间提供了创建高性能多线程应用程序所必需的所有工具,但要想有效地使用这些工具,需要有丰富的使用多线程软件工程的经验。对于相对简单的多线程应用程序,BackgroundWorker
组件提供了一个简单的解决方案。对于更复杂的异步应用程序,考虑实现一个符合基于事件的异步模式的类。
基于事件的异步模式具有多线程应用程序的优点,同时隐藏了多线程设计中固有的许多复杂的问题。使用EAP模式的能够:
支持基于事件的异步模式的类将具有一个或多个命名为 MethodNameAsync
的方法。 这些方法可能会创建同步版本的镜像,这些同步版本会在当前线程上执行相同的操作。 该类还可能具有 MethodNameCompleted
事件,并且可能会具有 MethodNameAsyncCancel
(或只是 CancelAsync)方法。
EAP常用的组件:
许多组件都支持异步执行工作,常用的有SoundPlayer和PictureBox组件,可以“在后台”加载音频和图像,同时主线程继续运行而不中断。
使用IAsyncResult
设计模式的异步操作时通过名微BeginOperatiobName
和EndOperationName
的两个方法来实现的,这两个方法分别开始和结束异常操作OperationName。例如:FileStream
类提供BeginRead
和EndRead
方法来从文件异步读取字节。
在调用BeginOperationName
后,应用程序可以继续在调用线程上执行指令,同时异步操作在另一个线程上执行。每次调用BeginOperationName
时,应用程序还调用EndOperationName
来获取操作的结果。
BeginOperationName
方法开始异步操作OperationName,并返回实现IAsyncResult
接口的对象。IAsyncResult
对象存储有关异步操作的信息。
编号 | 成员 | 描述 |
---|---|---|
1 | AsyncState | 一个特定应用程序的可选对象,其中包含有关异步操作的信息 |
2 | AsyncWaitHandle | 一个WaitHandle,可用来在异步操作完成之前阻止应用程序执行。 |
3 | CompletedSynchronously | 一个指,指示异步操作是否是在用于调用BeginOperationName的线程上完成,而不是在单独的ThreadPool 线程上完成。 |
4 | IsCompleted | 一个值,指示异步操作是否已完成。 |
BeginOperationName
方法采用该方法的同步版本的签名中声明的任何参数(由值传递或由引用传递)。 BeginOperationName
方法签名中不包含任何输出参数。 BeginOperationName
方法签名另外还包括两个其他参数。 第一个参数定义一个 AsyncCallback
委托,此委托引用在异步操作完成时调用的方法。 如果调用方不希望在操作完成后调用方法,它可以指定 null
(在 Visual Basic 中为Nothing
)。 第二个参数是一个用户定义的对象。 此对象可用来向异步操作完成时调用的方法传递应用程序特定的状态信息。 如果 BeginOperationName
方法还采用其他一些操作特定的参数(例如,一个用于存储从文件读取的字节的字节数组),则 AsyncCallback
和应用程序状态对象将是 BeginOperationName
方法签名中的最后两个参数。
BeginOperationName
立即返回对调用线程的控制。 如果 BeginOperationName
方法引发异常,则会在开始异步操作之前引发异常。 如果 BeginOperationName
方法引发异常,则意味着没有调用回调方法。
EndOperationName
方法用于结束异步操作OperationName
。EndOperationName
方法的返回值与其同步对应方法的返回值类型相同。并且是特定于异步操作的。
EndOperationName
方法采用该方法同步版本的签名中声明的所有输出参数或引用参数。
如果调用EndOperationName
时IAsyncResult
对象表示的异步操作尚未完成,则EndOperationName
将在异步操作完成之前阻止调用线程。异步操作引发的异常是从EndOperationName
方法引发的。未定义多次使用同一IAsyncResult
调用EndOperationName
方法的效果。