跨线程访问可视化控件的基本方法

版权声明:本文为博主原创文章,转载请注明出处: https://blog.csdn.net/IntegralforLove/article/details/83028495

可视化控件的Invoke和BeginInvoke方法

当我们在线程函数中写代码直接访问UI控件的属性和调用它们的方法时,结果无一例外,都会得到Visual Studio给出的同样的报错信息。

 引发上述异常的原因在于TextBox控件是由主线程创建的,不能直接从另一个线程访问。

在Windows应用程序中,绘制窗体和控件是由“UI线程”负责的,因此Windows不允许其它线程直接访问可视化控件。其原因是Windows无法控制其它线程将如何使用这些控件,而对控件某些属性的设置和方法调用有可能直接影响到控件的外观。这样想想就明白了:如果UI线程正在绘制按钮的同时,另一个线程要修改按钮上的文字,第三个线程又尝试着修改此按钮的背景色,事情会不会弄得一团糟?这个按钮能“画”好吗?

因此,在.NET中有这样一个基本编程原则:

不能跨线程直接访问窗体和控件,对它们的访问必须转由UI线程来负责处理。

在.NET Framework中,所有可视化的控件(包括窗体)都是从System.Windows.Forms.Control类派生出来的,考虑到跨线程访问控件的需要,Control类提供了相应的方法完成跨线程更新界面工作。

1.Invoke同步方法

Control.Invoke方法定义如下:

public object Invoke(Delegate method);

Invoke方法的参数是一个委托,代表在创建控件的线程中要执行的方法。

创建两个线程MyJob1与MyJob2,MyJob1线程每200ms计数一次,MyJob2线程每500ms计数一次,并将计数的结果显示在主窗体上。同时主线程可以执行输入输出操作,没有阻塞现象发生。

public partial class Form1 : Form
{
    private long m_nCount1, m_nCount2 = 0;
    private Thread m_Job1;		//定义线程1
    private Thread m_Job2;		//定义线程2

    private void MyJob1()		//线程函数1
    {
        for (int i = 0; i < 100000; i++)
        {
            m_nCount1 = m_nCount1 + 1;
            m_txt_Job1.Invoke((Action)delegate() { this.m_txt_Job1.Text = m_nCount1.ToString(); });  //跨线程访问UI控件,使用匿名方法
            Thread.Sleep(200);
        }
    }

    private void MyJob2()        //线程函数2
    {
        for (int i = 0; i < 100000; i++)
        {
            m_nCount2 = m_nCount2 + 1;
            m_txt_Job2.Invoke((Action)delegate() { this.m_txt_Job2.Text = m_nCount2.ToString(); });  //跨线程访问UI控件,使用匿名方法
            Thread.Sleep(500);
        }
    }
        
    private void m_btn_Set_Click(object sender, EventArgs e)
    {
        m_lbl_Output.Text = m_txt_Input.Text;    //主线程显示
    }

    private void m_btn_Start_Click(object sender, EventArgs e)
    {
        m_Job1 = new Thread(MyJob1);	
        m_Job2 = new Thread(MyJob2);
        m_Job1.Priority = ThreadPriority.Highest;       //设置线程1为最高优先级
        m_Job2.Priority = ThreadPriority.Lowest;        //设置线程2为最高低先级
        m_Job1.IsBackground = true;     //设置为背景线程(主线程结束时让CRL自动地强行结束所有还在运行的辅助线程)
        m_Job1.Start();     //启动线程
        m_Job2.Start();
        m_btn_Start.Enabled = false;
        m_btn_Stop.Enabled = true;
    }

    private void m_btn_Stop_Click(object sender, EventArgs e)
    {
        m_Job1.Abort();     //终止线程
        m_Job2.Abort();
        m_btn_Start.Enabled = true;
        m_btn_Stop.Enabled = false;
    }

    private void m_btn_Reset_Click(object sender, EventArgs e)
    {
        m_nCount1 = 0;
        m_nCount2 = 0;
        m_txt_Job1.Text = "0";
        m_txt_Job2.Text = "0";
    }
}

2.BeginInvoke方法

跨线程异步访问UI控件的方法——BeginInvoke,使用此方法,工作线程可以将一个方法传送个UI线程执行之后,继续执行下一步的任务而无需等待。注意,UI线程主要职责是更新用户界面和接收用户响应,它是使用串行方式从消息队列中提取消息并处理的,如果UI线程处理某个消息或执行其它线程穿丝过来的方法时间较长,将会导致用户界面停止响应,会让用户误以为死机了。因此,每项需要让UI线程执行的任务都不应该耗费过长的时间。对于的确需要运行较长时间的任务,可以使用独立的工作线程来完成,而只将处理结果交给UI线程显示。

BeginInvoke的使用方法和Invoke一样,只需将调用Invoke的那句代码改为BeginInvoke即可。

//m_txt_Job2.Invoke((Action)delegate() { this.m_txt_Job2.Text = m_nCount2.ToString(); });  
m_txt_Job2.BeginInvoke((Action)delegate() { this.m_txt_Job2.Text = m_nCount2.ToString(); });   //异步调用方法

//m_txt_Job1.Invoke((Action)delegate() { this.m_txt_Job1.Text = m_nCount1.ToString(); });   
m_txt_Job1.BeginInvoke((Action)delegate() { this.m_txt_Job1.Text = m_nCount1.ToString(); });  //异步调用方法   

3.通过回调函数向跨线程访问控件的方法传送参数

Control类的Invoke方法有另一个重载的形式:

public object Invoke(Delegate method, params object[] args);

第一个参数为委托变量所引用的回调函数,第二个参数即为需要方法传送的参数。

定义一个委托变量CallbackJob引用__ShowMessage函数:

private Action<object, long> CallbackJob;   	//Action<>泛型委托
public Form1()
{
    InitializeComponent();
    CallbackJob = new Action<object, long>(__ShowMessage);		//挂接
}

private void __ShowMessage(object txtJob,long count)
{
    (txtJob as TextBox).Text = count.ToString();
    Thread.SpinWait(1);
}

调用Invoke方法传入控件与数值。

m_txt_Job1.Invoke(CallbackJob, m_txt_Job1, m_nCount1);       //传入参数

m_txt_Job2.Invoke(CallbackJob, m_txt_Job2, m_nCount2);       //传入参数

猜你喜欢

转载自blog.csdn.net/IntegralforLove/article/details/83028495