异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序可在异步方法执行其任务时继续执行。
.NET Framework 为异步操作提供两种设计模式:
-
使用 IAsyncResult 对象的异步操作。
-
使用事件的异步操作。
IAsyncResult 设计模式允许多种编程模型,但更加复杂不易学习,可提供大多数应用程序都不要求的灵活性。可能的话,类库设计者应使用事件驱动模型实现异步方法。在某些情况下,库设计者还应实现基于 IAsyncResult 的模型。
有关使用事件的异步操作的文档,请参见基于事件的异步模式概述。
.NET Framework 的许多方面都支持异步编程功能,这些方面包括:
-
文件 IO、流 IO、套接字 IO。
-
网络。
-
远程处理信道(HTTP、TCP)和代理。
-
使用 ASP.NET 创建的 XML Web services。
-
ASP.NET Web 窗体。
-
使用 MessageQueue 类的消息队列。
使用 IAsyncResult 设计模式的异步操作是通过名为 BeginOperationName 和 EndOperationName 的两个方法来执行的,这两个方法分别开始和结束异步操作 OperationName。例如,FileStream 类提供 BeginRead 和 EndRead 方法来从文件异步读取字节。这两个方法实现了 Read 方法的异步版本。
在调用 BeginOperationName 后,应用程序可以继续在调用线程上执行指令,同时异步操作在另一个线程上执行。每次调用 BeginOperationName 时,应用程序还应调用 EndOperationName 来获取操作的结果。
开始异步操作
Begin OperationName 方法开始异步操作 OperationName 并返回一个实现 IAsyncResult 接口的对象。IAsyncResult 对象存储有关异步操作的信息。下表提供了有关异步操作的信息。
成员 | 说明 |
---|---|
AsyncState |
一个可选的应用程序特定的对象,包含有关异步操作的信息。 |
AsyncWaitHandle |
一个 WaitHandle,可用来在异步操作完成之前阻止应用程序执行。 |
CompletedSynchronously |
一个值,指示异步操作是否是在用于调用 BeginOperationName 的线程上完成,而不是在另一个ThreadPool 线程上完成。 |
IsCompleted |
一个值,指示异步操作是否已完成。 |
Begin OperationName 方法带有该方法(由值传递或由引用传递)的同步版本的签名中声明的任何参数。BeginOperationName 方法签名中不包含任何输出参数。BeginOperationName 方法签名另外还包括两个参数。在这两个参数中,第一个参数定义一个AsyncCallback 委托,public delegate void AsyncCallback (IAsyncResult ar )
此委托引用在异步操作完成时调用的方法。如果调用方不希望在操作完成后调用方法,它可以指定 null(在 Visual Basic 中为 Nothing)。第二个参数是一个用户定义的对象。此对象可用来向异步操作完成时调用的方法传递应用程序特定的状态信息。如果 BeginOperationName 方法还带有其他一些操作特定的参数(例如,一个用于存储从文件读取的字节的字节数组),则AsyncCallback 和应用程序状态对象将是 BeginOperationName 方法签名中的最后两个参数。
开始 OperationName 立即返回对调用线程的控制。如果 BeginOperationName 方法引发异常,则会在开始异步操作之前引发异常。如果 BeginOperationName 方法引发异常,则意味着没有调用回调方法。
结束异步操作
End OperationName 方法可结束异步操作 OperationName。EndOperationName 方法的返回值与其同步副本的返回值类型相同,并且是特定于异步操作的。例如,EndRead 方法返回从 FileStream 读取的字节数,EndGetHostByName 方法返回包含有关主机的信息的IPHostEntry 对象。EndOperationName 方法带有该方法同步版本的签名中声明的所有输出参数或引用参数。除了来自同步方法的参数外,EndOperationName 方法还包括 IAsyncResult 参数。调用方必须将对应调用返回的实例传递给 BeginOperationName。
如果调用 EndOperationName 时 IAsyncResult 对象表示的异步操作尚未完成,则 EndOperationName 将在异步操作完成之前阻止调用线程。异步操作引发的异常是从 EndOperationName 方法引发的。未定义多次使用同一 IAsyncResult 调用 EndOperationName 方法的效果。同样,也未定义使用非相关的 Begin 方法返回的 IAsyncResult 调用 EndOperationName 方法的效果。
开始异步操作后如果要阻止应用程序,(线程在异步操作运行完毕后再运行)可以直接调用 End 方法,这会阻止应用程序直到异步操作完成后再继续执行。也可以使用 IAsyncResult 的 AsyncWaitHandle 属性,调用其中的WaitOne等方法来阻塞线程。这两种方法的区别不大,只是前者必须一直等待而后者可以设置等待超时。
如果不阻止应用程序,则可以通过轮循 IAsyncResult 的 IsCompleted 状态来判断操作是否完成, while (result.IsCompleted != true){ UpdateUserInterface(); }或使用 AsyncCallback 委托来结束异步操作。AsyncCallback 委托包含一个 IAsyncResult 的签名,回调方法内部再调用 End 方法来获取操作执行结果。
对于这两种未定义的情况,实施者应考虑引发 InvalidOperationException。 |
此设计模式的实施者应通知调用方异步操作已通过以下步骤完成:将 IsCompleted 设置为 true,调用异步回调方法(如果已指定一个回调方法),然后发送 AsyncWaitHandle 信号。 |
对于访问异步操作的结果,应用程序开发人员有若干种设计选择。正确的选择取决于应用程序是否有可以在操作完成时执行的指令。如果应用程序在接收到异步操作结果之前不能进行任何其他工作,则必须先阻止该应用程序进行其他工作,等到获得这些操作结果后再继续进行。若要在异步操作完成之前阻止应用程序,您可以使用下列方法之一:
-
从应用程序的主线程调用 EndOperationName,阻止应用程序执行,直到操作完成之后再继续执行。有关演示此方法的示例,请参见通过结束异步操作来阻止应用程序执行。
-
使用 AsyncWaitHandle 来阻止应用程序执行,直到一个或多个操作完成之后再继续执行。有关演示此方法的示例,请参见使用 AsyncWaitHandle 阻止应用程序的执行。
在异步操作完成时不需要阻止的应用程序可使用下列方法之一:
-
按以下方式轮询操作完成状态:定期检查 IsCompleted 属性,操作完成后调用 EndOperationName。有关演示此方法的示例,请参见轮询异步操作的状态。
-
使用 AsyncCallback 委托来指定操作完成时要调用的方法。有关演示此方法的示例,请参见使用 AsyncCallback 委托结束异步操作。
代码:
class Program
{
static void Main()
{
byte[] buf = new byte[10240000];
Console.WriteLine("no block");
using (FileStream fs = new FileStream(@"\\172.52.25.237\ccb (d)\Honeywell\Playbook.zip", FileMode.Open))
{
buf = new byte[10240000];
IAsyncResult iresult = fs.BeginRead(buf, 0, buf.Length, null, null);
//no block
for (int i = 1; i < 100; i++)
Console.Write(buf[i] + ",");
}
Console.WriteLine("\r\nblock until finish.");
using (FileStream fs = new FileStream(@"\\172.52.25.237\ccb (d)\Honeywell\Playbook.zip", FileMode.Open))
{
buf = new byte[10240000];
IAsyncResult iresult = fs.BeginRead(buf, 0, buf.Length, null, null);
int tmp = fs.EndRead(iresult); //block until finish.
for (int i = 10; i < 100; i++)
Console.Write(buf[i] + ",");
}
Console.WriteLine("\r\nblock 10ms.");
using (FileStream fs = new FileStream(@"\\172.52.25.237\ccb (d)\Honeywell\Playbook.zip", FileMode.Open))
{
buf = new byte[10240000];
IAsyncResult iresult = fs.BeginRead(buf, 0, buf.Length, null, null);
iresult.AsyncWaitHandle.WaitOne(10);//block 10ms.
for (int i = 100000; i < 100000 + 100; i++)
Console.Write(buf[i] + ",");
}
Console.WriteLine("\r\nuse while loop as spin");
using (FileStream fs = new FileStream(@"\\172.52.25.237\ccb (d)\Honeywell\Playbook.zip", FileMode.Open))
{
buf = new byte[10240000];
IAsyncResult iresult = fs.BeginRead(buf, 0, buf.Length, null, null);
while (!iresult.IsCompleted)//do nothing until it finishes
{
}
for (int i = 1; i < 100; i++)
Console.Write(buf[i] + ",");
}
Console.WriteLine("\r\ncall AsyncCallback");
using (FileStream fs = new FileStream(@"\\172.52.25.237\ccb (d)\Honeywell\Playbook.zip", FileMode.Open))
{
buf = new byte[10240000];
IAsyncResult iresult = fs.BeginRead(buf, 0, buf.Length, mycallback, buf);
//Thread.Sleep(2000);//guarantee the async callback function works before the main thread finishes
while (!iresult.IsCompleted)//do nothing until it finishes
{
Console.Write(".");
Thread.Sleep(500);
}
}
Console.WriteLine("\r\ncall lamuda");
using (FileStream fs = new FileStream(@"\\172.52.25.237\ccb (d)\Honeywell\Playbook.zip", FileMode.Open))
{
buf = new byte[10240000];
IAsyncResult iresult = fs.BeginRead(buf, 0, buf.Length, (result) =>
{
Console.WriteLine("lamuda");
byte[] tmpbuf = (byte[])result.AsyncState;
Console.WriteLine(result.IsCompleted);
for (int i = 1; i < 10; i++)
Console.Write(tmpbuf[i] + ",");
}, buf);
//Thread.Sleep(2000);//guarantee the async callback function works before the main thread finishes
while (!iresult.IsCompleted)//do nothing until it finishes
{
Console.Write(".");
Thread.Sleep(500);
}
}
}
static void mycallback(IAsyncResult result)
{
Console.WriteLine("mycallback");
byte[] buf = (byte[])result.AsyncState;
Console.WriteLine(result.IsCompleted);
for (int i = 1; i < 10; i++)
Console.Write(buf[i] + ",");
}