增肥的叮噹

导航

 

编写桌面程序的时候当遇到长时间的方法调用时,大家都会遇到主线程被挂起、界面无响应的状况。线程操作是解决类似问题的绝佳手段,但对于刚接触线程的朋友来讲编写和调试多线程程序是非常痛苦的,尤其是在VC的MFC下,没有一定的C++基础,很难做出专业的多线程程序来。

 

幸好,.Net框架为我们提供了非常简便的方法和机制来创建多线程程序,譬如:Thread、线程池、异步方法调用以及BackgroundWorker!Thread和BackgroundWorker在MSDN中关于这两个类都有非常详细的解释以及丰富的示例,在后续的文章里我们会利用非常实际的示例程序来进行实战。在这里我们首先讨论异步方法调用。

 

所谓异步方法调用英文即为Asynchronous Method Invocation,讨论异步方法调用实际上就是讨论如何以非阻塞的方式来调用方法。常规的阻塞式方法调用例如:

 

        //调用同一个耗时的方法

        private void a()

        {

            Thread.Sleep(10000);

        }

 

        //阻塞式调用

        private void button1_Click(object sender, EventArgs e)

        {

            a();

            MessageBox.Show("button1_Click will return!");

        }

 

当单击button1后,主界面立即失去响应,持续10s后会显示一个消息框报告button1_Click方法将要返回,这时a()方法已经调用结束并返回。这表明主线程是在按照button1_click->a()->button1_click的顺序依次执行代码,所有代码都在主线程中执行。

 

然后,我们在窗体上放置第二个button,即button2,来演示如何进行异步方法调用。.Net2.0提供了两种途径实现异步方法调用,每种途径实际上都是使用委托来实现。一种是简单的无参数传递的MethodInvoker委托类型,另一种是标准的delegate类型。MethodInvoker类型的委托不接受任何参数传递,而delegate类型的委托使用起来则相当灵活,并且可以传递任意类型的参数。我们主要以MethodInvoker为例,首先看一下button2_Click事件的内容:

 

        //异步方法调用  by MethodInvoker

        private MethodInvoker simpleDelegate1;

 

        private void button2_Click(object sender, EventArgs e)

        {

            simpleDelegate1 = new MethodInvoker(a);

            simpleDelegate1.BeginInvoke(null, null);

            MessageBox.Show("button2_Click will return!");

        }

 

当单击button2后,会立即显示一个消息框报告button1_Click方法将要返回,而主界面没有失去响应。这表明在主线程下的button2_click要返回了!而这时的a()方法正在另一个独立的线程上继续执行着,而且在执行的10s过程中,我们仍然可以随意拖动窗体、改变窗体大小、继续单击窗体上的按钮甚至关闭窗体。这种调用即称为异步方法调用。

 

使用c++的朋友都知道函数指针,其实.Net下的委托就是一个函数指针类型,在.Net下已经没有函数的概念了,所以也称为方法指针,只不过框架把一些东西隐藏了,我们不必关心细节,不必用指针的方式显示声明罢了。其实委托也可以同步阻塞的方式执行,你可以尝试将button2_Click中的代码:

 

       simpleDelegate1.BeginInvoke(null, null);

更改为:

       simpleDelegate1.Invoke();

 

其执行方式与button1_Click完全一样。

 

Invoke以同步方式开始执行委托,其参数必须与要执行的方法完全一致。BeginInvoke就是以异步方式开始执行委托,它与您需要执行的方法具有相同的参数,另外还有两个可选参数。在.Net下当你看到以Begin开头的方法时几乎都是以异步方式执行代码。而对应BeginInvoke的就是EndInvoke方法,EndInvoke的任务就是传递BeginInvoke的返回值,我们将在下一篇博文中详细介绍BeginInvoke和EndInvoke的使用,以及如何捕获异步方法调用的结束。

我们已经实现了简单的异步方法调用,但是还有两个关键问题无法解决,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!

本文转载于:草上爬 http://space.itpub.net/14325734 转载的请自觉 标明出处。

posted on 2013-07-05 14:16  三千小胖子  阅读(271)  评论(1)    收藏  举报