5.4.3 使用异步方式调用同步方法
.NET Framework提供了一种可以利用委托异步调用任何方法的技术,唯一的要求就是需要声明一个与要调用的方法具有相同签名的委托。对于任何一个方法,如果希望异步执行,最简单的方法就是通过调用委托的BeginInvoke方法开始异步执行,然后执行其他操作,最后调用委托的EndInvoke方法结束异步操作。由于EndInvoke直到异步操作完成后才返回,因此这种方法非常适合文件或网络操作。
1、声明与要调用的方法具有相同签名的委托
下面的代码说明了如何声明一个与要调用的方法具有相同签名的委托:
private BinaryReader br;
...
delegate void SendMessageDelegate(string message);
private void SendMessage(string message)
{
try
{
bw.write(message);
bw.flush();
}
catch
{
MessageBox.Show("发送失败!");
}
}
2、通过轮询方式检查异步调用是否完成
声明和SendMessage方法具有相同签名的委托以后,公共语言运行时就会自动为该委托定义BeginInvoke方法和EndInvoke方法。然后就可以异步调用SendMessage方法了。
调用BeginInvoke方法后,该方法会立即返回IAsyncResult类型的接口,从用户界面的服务线程中进行异步调用时,可以利用该接口的IsCompleted属性来通过轮询方式检查异步调用是否完成。在轮询过程中,BeginInvoke方法在ThreadPool中创建的线程会继续执行异步方法。例如:
private bool needExit;
...
SendMessageDelegate d = new SendMessageDelegate(SendMessage);
IAsyncResult result = d.BeginInvoke(message, null, null);
while(result.IsCompleted == false)
{
if(needExit)
{
break;
}
Thread.Sleep(50);
}
当然,代码中只是演示了调用BeginInvoke后,如何查询异步操作是否完成,实际上也可以在调用BeginInvoke后,执行其他任何代码。
BeginInvoke方法除了与要异步执行的方法具有相同的参数外,另外还有两个可选参数,第1个参数是一个AsyncCallback委托,该委托引用在异步调用完成时要调用的方法;第2个参数是一个用户定义的对象,该对象将消息传入回调方法。在这段代码中,由于用不到这两个参数,所以全部将其设置为null。
程序调用BeginInvoke后,会立即返回一个可用于监视异步调用进度的IAsyncResult 接口,并继续执行BeginInvoke方法后面的代码,而不是等待异步调用完成。
3、使用EndInvoke结束异步调用
EndInvoke方法用于检索异步调用的结果,并结束异步调用。调用BeginInvoke之后,随时可以调用该方法。如果异步调用尚未完成,则EndInvoke会一直阻止该调用线程,直到异步调用完成。例如,在退出轮询后,可以直接通过下面的代码结束异步调用:
d.EndInvoke(result);
与其他异步操作的End方法相同,调用EndInvoke方法后,在EndInvoke方法返回前,由于有可能会引起调用EndInvoke方法的线程阻塞,所以一般不要从服务于用户界面的线程直接调用该方法,否则会在异步操作完成前,给用户一个界面无反应的错觉。为了使界面操作流畅,可以将上面的语句该为用其它线程执行,例如:
private struct SendMessageStates
{
public SendMessageDelagate d;
public IAsyncResult result;
}
private void AsyncSendMessage(string message)
{
SendMessageDelagate d = new SendMessageDelagate(SendMessage);
IAsyncResult result = d.BeginInvoke(message, null, null);
while(result.IsCompleted == false)
{
Thread.Sleep(50);
}
SendMessageStates states = new SendMessageStates();
states.d = d;
states.result = result;
Thread t = new Thread(FinishAsyncSendMessage);
t.IsBackground = true;
t.Start(states);
}
private void FinishAsyncSendMessage(object obj)
{
SendMessageStates states = (SendMessageStates)obj;
states.d.EndInvoke(states.result);
}
4、在异步调用中传递多个参数
在异步调用中,如果有多个参数信息,这些参数还可以使用out和ref关键字。例如:
delegate void ReceiveMessageDelegate(out string receiveMessage);
private void ReceiveMessage(out string receiveMessage)
{
receiveMessage = null;
try
{
receiveMessage = br.ReadString();
}
catch(Exception ex)
{
MessageBox.Show(ex.message);
}
}
...
ReceiveMessageDelegate d = new ReceiveMessageDelegate(ReceiveMessage);
IAsyncResult result = d.BeginInvoke(out receiveString, null, null);
while(result.IsCompleted == false)
{
Thread.Sleep(250);
}
d.EndInvoke(out receiveString, result);
可见,使用异步方式调用同步方法,既实现了任何方法的异步调用,又可以轻而易举地解决异步调用中的同步问题,对于相对比较复杂的异步处理过程,这是首选的方法,也是最简单、最方便的方法。