C#异步跨线程

最近学习C#的异步操作,总是搞不明白什么意思,然后今天想自己写一下整理一下思路。
在窗体编程中,经常会碰到界面假死的状态,原因是什么呢?

首先我们看一个造成假死的一个例子
我写了一个工程,目的是在点击开始按钮后progressBar.Value每隔10秒加一,然后richTextBox打印出来当前的progressBar.Value
部分代码如下

        private void UpdataUI()
        {
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(1 * 1000);    //长时间请求 
                progressBar.Value = i;
                richTextBox.AppendText(i.ToString() + "\n");
                richTextBox.SelectionStart = richTextBox.Text.Length; 
                richTextBox.ScrollToCaret(); 
            }                
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            UpdataUI();
        }

此时如果运行程序,点击开始后,界面将会造成假死

其次我们来讨论一下Windows的消息机制
我们知道窗体中用户的更种操作都是基于消息传输的,在Win32中我们差不多都会见到这段代码

/* Enter the modified message loop */
while (GetMessage (&msg, NULL, 0, 0))
{
    if (!TranslateMDISysAccel (hwndClient, &msg) &&
      !TranslateAccelerator (hwndFrame, hAccel, &msg))
    {
       TranslateMessage (&msg);
       DispatchMessage (&msg);
    }
}

上面的意思就是说GetMessage()将消息队列中的消息依次取出,然后转换成可以理解的东西,然后分发出去执行在switch中执行自己的代码

switch (message)
{

    case WM_CREATE:

    case WM_COMMAND:

    case WM_PAINT:
    .
    .
    .
    .......
}

用户的输入就是靠消息循环来执行的,如果现在有一个很耗时的操作也在消息循环中执行,即我们通常所说的UI线程中,那么后面的操作就会得不到响应,上面的代码中等待10秒的操作就是在UI线程中执行的,所以出现了所谓的假死,碰到这种情况就需要异步操作。

现在我们来用异步的方法解决这个问题
当你定义一个委托后,编译器会自动的为你生成一个类,还会为你在这个类里提供一个BeginInvoke方法和一个EndInvoke方法,这两个方法的实现是由CLR提供的,而这个BeginInvoke和EndInvoke只是起一个包装的作用。现在我们就用这两个方法来实现异步操作

        public Form1()
        {
            InitializeComponent();
            checkForIllegalCrossThreadCalls = false;//先加上,后面解释
        }
        private void UpdataUI()
        {
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(1 * 1000);    //长时间请求                
                progressBar.Value = i;
                richTextBox.AppendText(i.ToString() + "\n");
                richTextBox.SelectionStart = richTextBox.Text.Length; 
                richTextBox.ScrollToCaret(); 
            }                
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            Action act = new Action(UpdataUI);          
            act.BeginInvoke(null, null);//实现异步,相当于创建一个后台的工作线程来等待更新界面
        }

我们发现界面不在假死,一切正常了。
上面的异步操作没有返回值,但是通常我们在异步后会等待一个返回值,我们稍微修改一下代码让其有一个返回值

        public Form1()
        {
            InitializeComponent();
            checkForIllegalCrossThreadCalls = false;//先加上,后面解释
        }
        private string UpdataUI()
        {
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(1 * 1000);    //长时间请求
                //ProgressBarCalc(i);
                richTextBox.AppendText(i.ToString() + "\n");
                progressBar.Value = i;
            }
            return "Finshed";    
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            Func<string> act = new Func<string>(UpdataUI);
            IAsyncResult asyncResult = act.BeginInvoke((result) => 
            { 
                string ret = act.EndInvoke(result);
                MessageBox.Show(ret);
            }, null);//实现异步,相当于创建一个后台的工作线程来等待更新界面
        }

在这里来说明一下BeginInvoke
此方法将“异步”执行委托所指向的那个方法。所谓“异步”,就是结果并不是像调用“Invoke”方法一样直接就出现结果,BeginInvoke将在内部开辟一个新线程(注意:是后台线程!)去执行这个委托方法。因为是后台线程,因此如果主程序一旦关闭或者停止,无论后台线程的任务是否执行完毕,都将自动终止。本示例因为是WinForm,一旦运行主线程不会自动结束,但是对于诸如控制台一类的程序则不然,因此控制台程序如果使用BeginInvoke方法,必须要在其后面调用对应的EndInvoke。EndInvoke方法会阻塞当前进程,直至BeginInvoke执行完毕所有的委托方法后直接返回一个结果。

在上面的程序中我们加入了一句代码

checkForIllegalCrossThreadCalls = false;

那么这句是干什么用的呢,不妨先去掉然后运行一下,突然就报异常了,如何跨线程调用Windows窗体控件
通过调试我们发现程序主要检查checkForIllegalCrossThreadCalls、inCrossThreadSafeCall两个字段以及InvokeRequired,InvokeRequired的职责是判断当前运行的线程是不是与窗体主线程是同一个线程。
通过字面理解checkForIllegalCrossThreadCalls的意思就是“跨线程调用时是不是检查”。而这个字段在Control的静态构造函数里被设置为:checkForIllegalCrossThreadCalls = Debugger.IsAttached(checkForIllegalCrossThreadCalls 还通过CheckForIllegalCrossThreadCalls属性公开了 ),现在明白了为啥在Visual Studio里调试程序报异常,而独立运行却不报了吧。
那么该怎么解决呢,我们还是用异步的方法解决,但是现在要用Control.Invoke和Control.BeginInvoke这个是干什么的呢?这个的创建的异步线程是指相对于创建线程的线程是异步的,说的简单点就是指我们在UI线程中使用delegate.BeginInvoke的方法创建了一个相对于UI线程异步的线程,在异步线程中创建了一个相对于delegate.BeginInvoke异步的线程,即UI线程(我真心讲不清楚了,也不知道具体原因)。

        private void ProgressBarCalc(int value)
        {    
            if(this.InvokeRequired)//判断是否与UI线程在同一线程,如果不是就创建一个代理
            {
                this.Invoke(new Action<int>(ProgressBarCalc),value);
            }
            else//如果是就直接更新
            {
                richTextBox.AppendText(value.ToString() + "\n");
                progressBar.Value = value;
            }
        }

再次运行后我们发现一起OK
献出所有代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApplication9
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void ProgressBarCalc(int value)//通过异步更新界面,跨线程调用Windows窗体控件
        {    
            if(this.InvokeRequired)//判断是否与UI线程在同一线程,如果不是就创建一个代理
            {
                this.Invoke(new Action<int>(ProgressBarCalc),value);
            }
            else//如果是就直接更新
            {
                richTextBox.AppendText(value.ToString() + "\n");
                progressBar.Value = value;
            }
        }

        private string UpdataUI()//长时间的操作
        {
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(1 * 1000);    //长时间请求
                ProgressBarCalc(i);        //更新界面
            }
            return "Finshed";    
        }

        private void btn_Start_Click(object sender, EventArgs e)
        {
            Func<string> act = new Func<string>(UpdataUI);
            IAsyncResult asyncResult = act.BeginInvoke((result) => 
            { 
                string ret = act.EndInvoke(result);//执行完异步操作后得到返回结果
                MessageBox.Show(ret);//显示出结果
            }, null);//实现异步,相当于创建一个后台的工作线程来等待更新界面
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值