在上一篇博文中,我们已经实现了简单的异步方法调用,但是还有两个关键问题无法解决,1、我们无法知道异步方法何时执行完毕;2、必须控制主线程上的button对象的enabled属性,也就是说在非阻塞模式下不能对同一个button反复的单击。
我们注意到BeginInvoke方法所接受的另外两个可选参数,第一个参数就是我们所感兴趣的,它接受类型为AsyncCallBack类型的方法 – CallBack1!当异步方法调用结束时会自动调用这个回调方法。回调方法实际上是在Win32开发时代很多Windows API函数经常用到的,某些API函数需要传递一个回调函数的地址,当系统API执行完毕后自动执行自定义的回调函数。
回调方法传递过来一个IAsyncResult类行的参数ar,它表示异步方法操作的状态,IAsyncResult有一个非常有用的属性IsCompleted,我们可以根据这个属性的值来判断异步方法操作是否结束,继而EndInvoke异步方法。CallBack1方法也许应该这么写:
private void CallBack1(IAsyncResult ar)
{
if (ar.IsCompleted)
{
simpleDelegate1.EndInvoke(ar);
MessageBox.Show("Asynchronous Method Invocation is OK.");
}
}
但是运行结果会发现,MessageBox消息框并不是模式化的!显示消息框的同时,主窗体仍然可以获得焦点,窗体的对象也同样也可以操作。原来,这是在另一个独立线程上执行的MessageBox,而并不是在主线程上。那么我们该如何显示一个模式化主窗体的消息框呢?答案很简单,必须实施线程间操作,这里还是要用到委托。我们创建一个要在主线程上执行的方法,并且为这个方法定义一个委托。然后必须检测控件的InvokeRequired属性,在我们的例子中就是this,也就是主窗体Form1,这样做的目的是判断调用线程是否和主线程位于同一线程中,若不是同一线程,必须用Invoke将方法封装起来以便在主线程中执行。代码如下:
private void CallBack1(IAsyncResult ar)
{
if (ar.IsCompleted)
{
simpleDelegate1.EndInvoke(ar);
MethodInvoker updateUI = delegate
{
MessageBox.Show(this, "Asynchronous Method Invocation is OK.");
};
if (this.InvokeRequired)
{
this.Invoke(updateUI);
}
else
{
updateUI();
}
}
}
上一篇的博文中关于MethodInvoker的使用并不是这样的啊?没错!这种形式在.Net 2.0下是新增的,MethodInvoker这句代码的意思是委托的类型以及实际方法的内容一次性定义完成。实际上下边的代码与前述MethodInvoker写法完全等同:
MethodInvoker updateUI;
private void ShowMsg()
{
MessageBox.Show(this, "Asynchronous Method Invocation is OK.");
}
private void CallBack1(IAsyncResult ar)
{
if (ar.IsCompleted)
{
simpleDelegate1.EndInvoke(ar);
updateUI = new MethodInvoker(ShowMsg);
if (this.InvokeRequired)
{
this.Invoke(updateUI);
}
else
{
updateUI();
}
}
}
挺有意思的吧?有兴趣的朋友不妨试一试。
第一个关键问题我们已经顺利解决,那么关于button的状态设置问题其实到这里我们也已经解决了,因为我们已经可以做到在一个线程上访问另一个线程的对象及方法了。完整的代码如下:
//调用同一个耗时的方法
private void a()
{
Thread.Sleep(10000);
}
//阻塞式调用
private void button1_Click(object sender, EventArgs e)
{
a();
MessageBox.Show("button1_Click will return!");
}
//异步方法调用 方法1
private MethodInvoker simpleDelegate1;
private void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
simpleDelegate1 = new MethodInvoker(a);
simpleDelegate1.BeginInvoke(CallBack1, null);
MessageBox.Show("button2_Click will return!");
}
private void CallBack1(IAsyncResult ar)
{
if (ar.IsCompleted)
{
simpleDelegate1.EndInvoke(ar);
MethodInvoker updateUI = delegate
{
MessageBox.Show(this, "Asynchronous Method Invocation is OK.");
button2.Enabled = true;
};
if (this.InvokeRequired)
{
this.Invoke(updateUI);
}
else
{
updateUI();
}
}
}
我们已经充分介绍了异步方法调用的使用,在下一篇博文中我们将继续探讨.Net多线程的新宠儿 BackgroundWorker!