精华内容
下载资源
问答
  • 线程访问Windows窗体控件,线程带多参数。 跨线程访问Windows窗体控件,线程带多参数。
  • 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问...
  • 再谈 Windows 窗体线程

    千次阅读 2005-05-01 18:03:00
    再谈 Windows 窗体线程Chris Sells 2002年9月2日 从 MSDN Code Center 下载 asynchcaclpi.exe 示例文件(英文)。摘要:本文探讨了如何利用多线程从长时间运行的操作中分离出用户界面 (UI),以将用户的后续输入...

    再谈 Windows 窗体多线程


    Chris Sells
    2002年9月2日

    从 MSDN Code Center 下载 asynchcaclpi.exe 示例文件(英文)。

    摘要:本文探讨了如何利用多线程从长时间运行的操作中分离出用户界面 (UI),以将用户的后续输入传递给辅助线程以调节其行为,从而实现稳定而正确的多线程处理的消息传递方案。

    或许您还能回想起以前的一些专栏,例如 Safe, Simple Multithreading in Windows Forms(英文)。如果您仔细阅读,就可以使 Windows 窗体和线程很好地协同工作。执行长时间运行的操作的较好方法是使用线程,例如计算 pi 小数点之后的多位数值(如以下图 1 所示)。

    图 1:Pi 的位数应用程序

    Windows 窗体和后台处理

    在上一篇文章中,我们介绍了直接启动线程进行后台处理,但选择使用异步委托来启动辅助线程。异步委托在传递参数时具有语法方便的优点,并且通过在进程范围的、公共语言运行库管理的池中使用线程来获得更大的作用范围。我们遇到的仅有的问题发生在辅助线程需要向用户通知进度时。在本例中,辅助线程不允许直接使用 UI 控件(长期使用的 Win32® UI 不被允许)。取而代之的是,辅助线程必须向 UI 线程发送或发布一条消息,并使用 Control.InvokeControl.BeginInvoke 在拥有 UI 控件的线程上执行代码。考虑到这些因素后的代码如下:

    // 委托以开始异步计算 pi
    delegate void CalcPiDelegate(int digits);
    void _calcButton_Click(object sender, EventArgs e) {
      // 开始异步计算 pi
      CalcPiDelegate calcPi = new CalcPiDelegate(CalcPi);
      calcPi.BeginInvoke((int)_digits.Value, null, null);
    }
    
    void CalcPi(int digits) {
      StringBuilder pi = new StringBuilder("3", digits + 2);
    
      // 显示进度
      ShowProgress(pi.ToString(), digits, 0);
    
      if( digits > 0 ) {
        pi.Append(".");
    
        for( int i = 0; i < digits; i += 9 ) {
          ...
          // 显示进度
          ShowProgress(pi.ToString(), digits, i + digitCount);
        }
      }
    }
    
    // 委托以向 UI 线程通知辅助线程的进度
    delegate
    void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar);
    
    void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
      // 确保在正确的线程上
      if( _pi.InvokeRequired == false ) {
        _pi.Text = pi;
        _piProgress.Maximum = totalDigits;
        _piProgress.Value = digitsSoFar;
      }
      else {
        // 异步显示进度
        ShowProgressDelegate showProgress =
          new ShowProgressDelegate(ShowProgress);
        this.BeginInvoke(showProgress,
          new object[] { pi, totalDigits, digitsSoFar });
      }
    }
    

    注意,这里有两个委托。第一个是 CalcPiDelegate,用于捆绑要传递给(从线程池中分配的)辅助线程上的 CalcPi 的参数。当用户决定要计算 pi 时,事件处理程序将创建此委托的一个实例。此工作通过调用 BeginInvoke 在线程池中进行排队。第一个委托实际上是由 UI 线程用于向辅助线程传递消息。

    第二个委托是 ShowProgressDelegate,由辅助线程用于向 UI 线程回传消息,通常是有关长时间运行的操作的最新进度。为了对调用者屏蔽与此 UI 线程有关的线程安全通信信息,ShowProgress 方法在此 UI 线程上通过 Control.BeginInvoke 方法使用 ShowProgressDelegate 给自己发送消息。Control.BeginInvoke 异步队列为 UI 线程提供服务,并且不等待结果就继续运行。

    取消

    在本示例中,我们可以在辅助线程和 UI 线程之间来回发送消息而无需关注外部环境。UI 线程不必等待辅助线程执行完毕,甚至无需等待完成通知,因为辅助线程在执行过程中会与其实时交流进度情况。同样,辅助线程也不必等待 UI 线程显示进度,只要进度消息按照固定的时间间隔发送以使用户感到满意即可。但有一点无法满足用户,即:不能完全控制应用程序正在执行的任何处理。即使 UI 在计算 pi 时能够提供响应,有时用户仍需要取消计算操作,例如如果用户决定需要计算 1,000,001 位数字但却错误地输入了 1,000,000。更新的 CalcPi UI 允许取消操作,如图 2 所示。

    图 2:允许用户取消长时间运行的操作

    要实现取消长时间运行的操作,需要完成多个步骤。首先,需要为用户提供 UI。在本例中,Calc(计算)按钮在计算开始后变为 Cancel(取消)按钮。另一个常见的选择是进度对话框。该对话框通常包含当前进度的详细信息,包括显示工作完成百分比的进度条和一个 Cancel(取消)按钮。

    如果用户决定取消操作,则应该在成员变量中提供说明,并且在从 UI 线程获知辅助线程应该停止时,到辅助线程自己知道并可以停止发送进度之前的这一小段时间内,应该禁用 UI。如果忽略这段时间,可能会出现这种情况:用户在第一个辅助线程停止发送进度之前又开始了另一项操作,这就使 UI 线程必须判断是从新的辅助线程获取进度还是从即将关闭的旧线程获取进度。当然,也可以为每个辅助线程分配一个唯一的 ID,从而使 UI 线程可以处理好这些工作。(如果有多个并存的长时间运行的操作,则很有必要这样做。)这样,在从 UI 获知辅助线程即将停止工作时到辅助线程获知之前的这一小段时间内,暂停 UI 通常会更容易一些。我们的简单的 pi 计算器的实现方式是使用一个具有三个值的枚举变量,如下所示:

    enum CalcState {
        Pending,     // 没有任何计算正在运行或取消
        Calculating, // 正在计算
        Canceled,    // 在 UI 中计算已被取消但在辅助线程中还没有
    }
    
    CalcState _state = CalcState.Pending;
    

    现在,根据所处的状态不同,我们分别处理 Calc 按钮,如下所示:

    void _calcButton_Click(...)  {
        // Calc 按钮兼有 Cancel 按钮的功能
        switch( _state ) {
            // 开始新的计算
            case CalcState.Pending:
                // 允许取消
                _state = CalcState.Calculating;
                _calcButton.Text = "Cancel";
    
                // 异步委托方法
                CalcPiDelegate  calcPi = new CalcPiDelegate(CalcPi);
                calcPi.BeginInvoke((int)_digits.Value, null, null);
                break;
    
            // 取消正在运行的计算
            case CalcState.Calculating:
                _state = CalcState.Canceled;
                _calcButton.Enabled = false;
                break;
    
            // 在取消过程中应该无法按下 Calc 按钮
            case CalcState.Canceled:
                Debug.Assert(false);
                break;
        }
    }
    

    请注意,如果在处于 Pending 状态时按下 Calc/Cancel 按钮,我们发送状态 Calculating(同时更改按钮上的标签),并像以前那样开始异步计算。如果在处于 Calculating 状态时按下 Calc/Cancel 按钮,则应该将状态切换为 Canceled 并禁止 UI 开始新的计算(在它为我们向辅助线程传递取消状态期间)。一旦我们已经向辅助线程传达了取消操作的信息,就可以再次启用 UI 并将状态重设为 Pending,从而使用户可以开始其他操作。要向辅助线程传达取消操作的信息,可以将 ShowProgress 方法扩充为包含新的 out 参数:

    void ShowProgress(..., out bool cancel)
    
    void CalcPi(int digits) {
        bool cancel = false;
        ...
    
        for( int i = 0; i < digits; i += 9 ) {
            ...
    
            // 显示进度(检查是否取消)
            ShowProgress(..., out cancel);
            if( cancel ) break;
        }
    }
    

    您可能想尝试将取消指示器设置为从 ShowProgress 返回的布尔值,但我从来都记不住 true 是表示取消还是表示一切正常(或继续照常执行)。所以我使用 out 参数,这样可以更直观一些。

    最后剩下的事情是更新 ShowProgress 方法(即在辅助线程和 UI 线程之间实际执行传递工作的那部分代码),以判断用户是否请求取消并相应地通知 CalcPi 程序。确切地说,如何在 UI 和辅助线程之间传递信息取决于我们希望使用哪种技术。

    通过共享数据进行通信

    传递 UI 当前状态的最常见方法是让辅助线程直接访问 _state 成员变量。我们可以使用以下代码来达到这一目的:

    void ShowProgress(..., out bool cancel) {
      // 不要这样做!
      if( _state == CalcState.Cancel ) {
        _state = CalcState.Pending;
        cancel = true;
      }
      ...
    }
    

    我希望您看到这段代码时能够自然而然地(而不只是因为代码中的警告注释)想到放弃它。如果您打算编写多线程的程序,就必须要注意在任何时候两个线程都可能会同时访问相同的数据(在本例中是 _state 成员变量)。在线程之间共享访问数据很容易使线程进入“竞争状态”,即其中一个线程在另一个线程完成更新数据之前抢先读取部分更新的数据。为了实现共享数据的并发访问,您需要监视共享数据的使用情况,以确保各线程耐心等待其他线程处理完数据。为了监视共享数据的访问,.NET 为共享对象提供了 Monitor 类,其作用类似于为数据加了一把锁(C# 中包含了这种方便的加锁块):

    object _stateLock = new object();
    
    void ShowProgress(..., out bool cancel) {
      // 也不要这样做!
      lock( _stateLock ) { // 监视锁
        if( _state == CalcState.Cancel ) {
          _state = CalcState.Pending;
          cancel = true;
        }
        ...
      }
    }
    

    现在我已经适当地锁定了对共享数据的访问,但由于我是采取上述方法来实现的,因此在执行多线程编程时就很可能会产生另一个常见问题,即“死锁”。当两个线程出现死锁时,在继续执行之前它们均会等待另一个线程完成其工作,这样实际上两者就都不能执行。

    如果所有这些有关竞争状态和死锁的讨论都已经引起了您的关注,那就好。通过共享数据进行的多线程编程很难做到十全十美。目前为止,我们已经能够避免这些问题,因为我们已经传递了该数据的很多副本,并且各线程对这些副本具有完全的所有权。如果没有共享数据,则无需考虑同步。如果您发现必须访问共享数据(也就是说,复制数据需要大量空间或非常费时),则需要研究在线程之间共享数据(查看“参考书目”一节以获得在此领域中我最喜欢的研究文章)。

    然而,绝大部分多线程方案(尤其是当涉及到 UI 多线程时)似乎与我们目前一直使用的简单消息传递方案配合得最好。大多数时候,您不希望 UI 对正在后台进行处理的数据具有访问权限(例如正在打印的文档或正被枚举的对象集合)。对于这些情况,最好的选择是避免使用共享数据。

    通过方法参数进行通信

    我们已经将 ShowProgress 方法扩充为包含 out 参数了。为什么不让 ShowProgress 在 UI 线程上执行时检查 _state 变量的状态呢?如下所示:

    void ShowProgress(..., out bool cancel) {
        // 确认在 UI 线程上
        if( _pi.InvokeRequired == false ) {
            ...
    
            // 检查是否取消
            cancel = (_state == CalcState.Canceled);
    
            // 检查是否完成
            if( cancel || (digitsSoFar == totalDigits) ) {
                _state = CalcState.Pending;
                _calcButton.Text = "Calc";
                _calcButton.Enabled = true;
    
            }
        }
        // 将控制传递给 UI 线程
        else { ... }
    }
    

    由于只有 UI 线程访问 _state 成员变量,因此不需要同步。现在只需要按照上述方法将控制传递给 UI 线程,即可获得 ShowProgressDelegatecancel out 参数。不幸的是,使用 Control.BeginInvoke 使情况变得有些复杂。问题在于 BeginInvoke 不会等待 ShowProgress 在 UI 线程上的调用结果,因此我们有两个选择。其中之一是向 BeginInvoke 传递另一个委托并在 ShowProgress 从 UI 线程返回后调用它,但这同时也会发生在线程池的其他线程上,所以我们还必须回到同步上来,这一次是在辅助线程和连接池中的另一个线程之间同步。另一个较为简单的方法是切换到同步的 Control.Invoke 方法并等待 cancel out 参数。然而,就算采用这种方法也会有一点点棘手,如以下代码所示:

    void ShowProgress(..., out bool cancel) {
        if( _pi.InvokeRequired == false ) { ... }
        // 将控制传递给 UI 线程
        else {
            ShowProgressDelegate  showProgress =
                new ShowProgressDelegate(ShowProgress);
    
            // 避免包装或丢失返回值
            object inoutCancel = false;
    
            // 同步显示进度(这样我们可以检查是否取消)
            Invoke(showProgress, new object[] { ..., inoutCancel});
            cancel = (bool)inoutCancel;
        }
    }
    

    虽然直接向 Control.Invoke 简单传递一个布尔变量来获得 cancel 参数可能是一个理想的方法,但这同样存在问题。问题是 bool 是“值数据类型”,而 Invoke 采用对象数组作为参数,并且对象是“引用数据类型”。(您可以查看“参考书目”一节以获得有关讨论两者区别的书籍。)其结果是作为对象传递的 bool 将被复制而保持实际的 bool 不变,这意味着我们无法知道操作被取消了。为了避免出现这种情况,我们创建了自己的对象变量 (inoutCancel) 并传递它,这样就避免了复制。在同步调用 Invoke 后,我们将 object 变量转换为 bool 以查看是否应该取消操作。

    任何时候调用带有 outref 参数的 Control.Invoke(或 Control.BeginInvoke)时,都必须注意值类型和引用类型数据之间的区别。(这里的 outref 是值类型,例如 intbool 等原始类型以及枚举和结构类型等。)当然,即便您使用自定义的引用类型(也叫做类)传递更加复杂的数据,也不需要专门再做其他工作。然而,即使在处理 Invoke/BeginInvoke 的数据类型时会有些麻烦,但相比让多线程代码在竞争状态或使用死锁-释放方法的情况下访问共享数据而言,这算不上是个大问题,所以我认为付出这点小代价是值得的。

    小结

    我们又一次使用了一个很小的示例来探讨一些复杂的问题。我们不仅利用了多线程从长时间运行的操作中分离 UI,而且还将用户的进一步输入传递给辅助线程以调整其行为。尽管我们原本可以使用共享数据来避免复杂的同步问题(这只有在您的上司试用您的代码时才会产生),但最终我们还是使用了消息传递方案来进行稳定而正确的多线程处理。

    参考书目

    展开全文
  • windows窗体控件进行线程进行安全调用,内含详细说明
  • 如何跨线程调用Windows窗体控件

    千次阅读 2010-12-15 18:33:00
    在开发具有线程的应用程序时,有时会通过子线程实现Windows窗体,以及控件的操作,比如:在对文件进行复制时,为了使用户可以更好的观察到文件的复制情况,可以在指定的Windows窗体上显示一个进度条,为了避免文件...

    在开发具有线程的应用程序时,有时会通过子线程实现Windows窗体,以及控件的操作,比如:在对文件进行复制时,为了使用户可以更好的观察到文件的复制情况,可以在指定的Windows窗体上显示一个进度条,为了避免文件复制与进度条的同时操作所带来的机器假死状态,可以用子线程来完成文件复制与进度条跟踪操作,下面以简单的例子在子线程中操作窗体中的TextBox控件。代码如下:


    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Linq;

    using System.Text;

    using System.Windows.Forms;

     

    using System.Threading;//添加线程的命名空间

     

    namespace ppp

    {

        public partial class Form1 : Form

        {

            public Form1()

            {

                InitializeComponent();

            }

     

            Thread t; //定义线程变量

     

            private void button1_Click(object sender, EventArgs e)

            {

                t = new Thread(new ThreadStart(Threadp)); //实例化线程

                t.Start();//启动线程

            }

        自定义方法Threadp,主要用于线程的调用。代码如下:

            public void Threadp()

            {

                textBox1.Text = "实现在子线程中操作主线程中的控件";

                t.Abort();//关闭线程

            }

    }

     

           图1  在子线程中操作主线程中控件的错误提示信息
     

    以上是通过一个子线程来操作主线程中的控件,但是,这样作会出现一个问题(如图1所示),就是TextBox控件是在主线程中创建的,在子线程中并没有对其进行创建,也就是从不是创建控件的线程访问它。那么,如何解决跨线程调用Windows窗体控件呢?可以用线程委托实现跨线程调用Windows窗体控件。下面将上一个例子进行一下改动。代码如下:

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Linq;

    using System.Text;

    using System.Windows.Forms;

     

    using System.Threading;//添加线程的命名空间

     

    namespace ppp

    {

        public partial class Form1 : Form

        {

            public Form1()

            {

                InitializeComponent();

            }

     

            Thread t; //定义线程变量

     

            private void button1_Click(object sender, EventArgs e)

            {

                t = new Thread(new ThreadStart(Threadp)); //实例化线程

                t.Start();//启动线程

            }

     

            private delegate void setText();//定义一个线程委托

       自定义方法Threadp,主要用于线程的调用。代码如下:

            public void Threadp()

            {

                setText d = new setText(Threading); //实例化一个委托

                this.Invoke(d); //在拥用此控件的基础窗体句柄的线程上执行指定的委托

            }

       自定义方法Threading,主要作于委托的调用。代码如下:

            public void Threading()

            {

                textBox1.Text = "实现在子线程中操作主线程中的控件";

                t.Abort();//关闭线程

            }

        }

    }

    展开全文
  • 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问...
  • 进行多线程处理时,如果{ }中有控件(textbox,combox.....),程序就会报错:不允许跨线程调用windows窗体控件,这时候我们使用delegate委托进行窗体控件的调用。 (1)如果控件出现在if()判断...

    当我们需要处理大量数据时,为了使窗体界面不出现假死状态,需要使用多线程进行处理。

    当利用线程池ThreadPool.QueueUserWorkItem(t=>{ });进行多线程处理时,如果{ }中有控件(textbox,combox.....),程序就会报错:不允许跨线程调用windows窗体控件,这时候我们使用delegate委托进行窗体控件的调用。

    (1)如果控件出现在if()判断条件里面

    delegate void ReadTextValue();

    private void btn_Input_Click(object sender, EventArgs e)
            {

                   ReadTextValue readtext = new ReadTextValue(CmbQueryValue); 

                   ThreadPool.QueueUserWorkItem(p =>
                             { 

                                        if (cmbQuery.InvokeRequired)
                                                  {
                                                        cmbQuery.Invoke(readtext); 

                                                         .....................

                                                  }

                             });

            } 

    private void CmbQueryValue()
            {
                cmbQuery.Text = "物料编码";        
            }    

    展开全文
  • 使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。 示例 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一

    原文链接:http://msdn.microsoft.com/zh-cn/library/ms171728(v=vs.80)

    如何:对 Windows 窗体控件进行线程安全调用

    使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。

    示例

    访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。

    .NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”

    此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。

    Note注意

    可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。

    下面的代码示例演示如何从辅助线程以线程安全方式和非线程安全方式调用 Windows 窗体控件。它演示一种以非线程安全方式设置 TextBox 控件的 Text 属性的方法,还演示两种以线程安全方式设置 Text 属性的方法。

     代码:

    Form1:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace CrossThreadDemo
    {
        public partial class Form1 : Form
        {
            //该代理允许异步调用来设置TextBox控件的Text属性
            delegate void SetTextCallback(string text);
    
            //该线程用于演示线程安全和非线程安全的调用Window窗体控件
            private Thread demoThread = null;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
    
            }
    
            #region 非线程安全
            //该事件处理过程(按钮点击事件处理过程)创建了一线程用来一非线程安全的方式调用Window窗体控件
            /// <summary>
            /// 非线程安全,按钮点击,事件处理
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void setTextUnsafeBtn_Click(object sender, EventArgs e)
            {
                this.demoThread =
                    new Thread(new ThreadStart(this.ThreadProcUnsafe));
    
                this.demoThread.Start();
    
            }
            //该方法在一个工人(次)线程上执行,并且非线程安全地调用了TextBox控件
            private void ThreadProcUnsafe()
            {
                this.textBox1.Text = "该文本被非线程安全的形式设置";
            }
            #endregion
    
            #region 线程安全
            //该事件处理过程创建了一线程以线程安全的方式调用Windows窗体控件
            /// <summary>
            /// 线程安全,按钮点击,事件处理
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void setTextSafeBtn_Click(object sender, EventArgs e)
            {
                this.demoThread =
                    new Thread(new ThreadStart(this.ThreadProcSafe));
    
                this.demoThread.Start();
    
            }
            //该方法在一个次线程上执行,并且线程安全地调用了TextBox控件
            private void ThreadProcSafe()
            {
                this.SetText("该文本被以线程安全的方式设置");
            }
            //如果调用Windows窗体控件(此处为TextBox)的线程不是创建该控件的线程,
            //该方法创建一个SetTextCallback(委托)并且通过该委托实例的Invoke方法异步地调用自己
            //如果调用控件的线程和创建该控件的线程是同一个线程,那么就直接设置TextBox的Text属性
            private void SetText(string text)
            {
                //InvokeRequired属性需要比较调用线程和创建线程的线程ID,如果俩线程ID不同,则返回true
                if (this.textBox1.InvokeRequired)//调用和创建该控件的线程是不同的线程,必须调用Invoke方法
                {
                    //将设置TextBox控件Text属性的方法本身作为构建委托对象的参数
                    //创建该方法的委托实例
                    SetTextCallback d = new SetTextCallback(SetText);
                    //调用该委托实例,并传递参数,参数为object类型,使用this调用Invoke(this为当前窗体,是创建该窗体控件的线程)
                    this.Invoke(d, new object[] { text });//this指定创建该控件的线程来Invoke(调用)
                }
                else//调用与创建该控件的线程是同一个线程
                {
                    this.textBox1.Text = text;
                }
            }
            #endregion
    
            #region 后台Worker
    
            //该事件处理过程(按钮点击),通过调用窗体中的BackgroundWorker组件实例的RunWorkerAsync方法
            //开始执行后台操作
            //当BackgroundWorker(后台线程)触发RunWorkerCompleted(工作线程完成)时,TextBox控件的Text属性被设置
            /// <summary>
            /// 后台Worker,按钮点击,事件处理
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
            {
                //调用窗体中的BackgroundWorker组件实例的RunWorkerAsync方法
                this.backgroundWorker1.RunWorkerAsync();
    
            }
            //该事件处理过程(后台线程的工作线程完成事件)设置TextBox控件的Text属性。
            //它由创建TextBox控件的线程调用,因此是线程安全的。
            //BackgroundWorker(后台工作线程)是处理异步操作的最好的方式
            private void backgroundWorker1_RunWorkerCompleted(
                object sender,
                RunWorkerCompletedEventArgs e)
            {
                this.textBox1.Text =
                    "该文本由后台线程安全地设置";
            }
            #endregion
        }
    }


    Form1.Designer:

    namespace CrossThreadDemo
    {
        partial class Form1
        {
            /// <summary>
            /// 必需的设计器变量。
            /// </summary>
            private System.ComponentModel.IContainer components = null;
    
            /// <summary>
            /// 清理所有正在使用的资源。
            /// </summary>
            /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }
    
            #region Windows 窗体设计器生成的代码
    
            /// <summary>
            /// 设计器支持所需的方法 - 不要
            /// 使用代码编辑器修改此方法的内容。
            /// </summary>
            private void InitializeComponent()
            {
                this.textBox1 = new System.Windows.Forms.TextBox();
                this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
                this.setTextUnsafeBtn = new System.Windows.Forms.Button();
                this.setTextSafeBtn = new System.Windows.Forms.Button();
                this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();
                this.SuspendLayout();
                // 
                // textBox1
                // 
                this.textBox1.Location = new System.Drawing.Point(18, 13);
                this.textBox1.Name = "textBox1";
                this.textBox1.Size = new System.Drawing.Size(251, 21);
                this.textBox1.TabIndex = 0;
                // 
                // backgroundWorker1
                // 
                this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
                // 
                // setTextUnsafeBtn
                // 
                this.setTextUnsafeBtn.Location = new System.Drawing.Point(16, 54);
                this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";
                this.setTextUnsafeBtn.Size = new System.Drawing.Size(75, 23);
                this.setTextUnsafeBtn.TabIndex = 1;
                this.setTextUnsafeBtn.Text = "非线程安全";
                this.setTextUnsafeBtn.UseVisualStyleBackColor = true;
                this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click);
                // 
                // setTextSafeBtn
                // 
                this.setTextSafeBtn.Location = new System.Drawing.Point(106, 54);
                this.setTextSafeBtn.Name = "setTextSafeBtn";
                this.setTextSafeBtn.Size = new System.Drawing.Size(75, 23);
                this.setTextSafeBtn.TabIndex = 2;
                this.setTextSafeBtn.Text = "线程安全";
                this.setTextSafeBtn.UseVisualStyleBackColor = true;
                this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
                // 
                // setTextBackgroundWorkerBtn
                // 
                this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(198, 54);
                this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";
                this.setTextBackgroundWorkerBtn.Size = new System.Drawing.Size(75, 23);
                this.setTextBackgroundWorkerBtn.TabIndex = 3;
                this.setTextBackgroundWorkerBtn.Text = "后台Worker";
                this.setTextBackgroundWorkerBtn.UseVisualStyleBackColor = true;
                this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click);
                // 
                // Form1
                // 
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(290, 90);
                this.Controls.Add(this.setTextBackgroundWorkerBtn);
                this.Controls.Add(this.setTextSafeBtn);
                this.Controls.Add(this.setTextUnsafeBtn);
                this.Controls.Add(this.textBox1);
                this.Name = "Form1";
                this.Text = "Form1";
                this.Load += new System.EventHandler(this.Form1_Load);
                this.ResumeLayout(false);
                this.PerformLayout();
    
            }
    
            #endregion
    
            private System.Windows.Forms.TextBox textBox1;
            private System.ComponentModel.BackgroundWorker backgroundWorker1;
            private System.Windows.Forms.Button setTextUnsafeBtn;
            private System.Windows.Forms.Button setTextSafeBtn;
            private System.Windows.Forms.Button setTextBackgroundWorkerBtn;
        }
    }


    步骤总结:

    对 Windows 窗体控件的非线程安全调用

    对 Windows 窗体控件的非线程安全调用方式是从辅助线程直接调用。调用应用程序时,调试器会引发一个 InvalidOperationException,警告对控件的调用不是线程安全的。

    (代码略,上边代码的非线程安全部分)

    对 Windows 窗体控件的线程安全调用

    对 Windows 窗体控件进行线程安全调用

    1. 查询控件的 InvokeRequired 属性。

    2. 如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke

    3. 如果 InvokeRequired 返回 false,则直接调用控件。

    在下面的代码示例中,此逻辑是在一个称为 SetText 的实用工具方法中实现的。名为 SetTextDelegate 的委托类型封装SetText 方法。TextBox 控件的 InvokeRequired 返回true 时,SetText 方法创建 SetTextDelegate 的一个实例,并调用窗体的Invoke 方法。这使得 SetText 方法被创建 TextBox 控件的线程调用,而且在此线程上下文中将直接设置Text 属性。

    (代码略,上边代码的线程安全部分)

    使用 BackgroundWorker 进行的线程安全调用

    在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。BackgroundWorker 组件使用事件驱动模型实现多线程。辅助线程运行 DoWork 事件处理程序,创建控件的线程运行 ProgressChanged RunWorkerCompleted 事件处理程序。注意不要从 DoWork 事件处理程序调用您的任何控件。

    下面的代码示例不异步执行任何工作,因此没有 DoWork 事件处理程序的实现。TextBox 控件的Text 属性在 RunWorkerCompleted 事件处理程序中直接设置。

    (代码略,上边代码的后台工作线程部分)

     

    备注(不敢谎称译注):

    该文章原文本是简体中文,但作为程序员,大家都明白,文字说明并不能让你了解多少内容,实例代码才是你能够学习到知识的地方,这也就是很多人的博客里简单的说上几句介绍,然后说“看代码”。对于读者来说,光看文字说明是看不懂的,对于作者来说,光写文字说明也是写不出想法的。只有把代码和说明放在一块,才方便读写,所有多数人在代码里写很多注释来说明,而不是在代码外写大段的注释。

    这篇文字原文是中文,但是代码的注释却都是英文,因此我尝试着把英文注释翻译为中文,在转载和翻译上犹豫了一会,最后无耻的选择了翻译。。。

    我没有什么本事,去翻译一篇好文章,只能自己硬着头皮读一些不得不读的E文文章或者代码,这次的情况也是这样,在自己将就着读下来以后,想把它汉化后备份下来,等到哪天需要的时候,再翻出来参考下。但放在自己的机子上并不是一个好的方法,东西乱糟糟的,过一段时间就找不到了,放在网上,自己容易找到,还可以让百度、谷歌帮忙查找。

    如果你链接到了这篇文章,然后找到了你想要的东西,那我很高兴,如果耽误了你的时间,我只能说句对不起了。

    QQ:373048914,希望和众多的菜鸟共同进步。

    展开全文
  • 如果使用多线程来提高 Windows 窗体应用程序的性能,则必须确保以线程安全方式调用控件。 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致...
  • Windows 窗体编程如何:对 Windows 窗体控件进行线程安全调用 使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。示例访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个...
  • 使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致...
  • c#中跨线程调用windows窗体控件 c#中如何跨线程调用windows窗体控件?我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题。然而我们并不能用传统方法来做这个问题,...
  • 我是要随机在窗体中使某个button的visible为true的,可是老是出现“线程间操作无效: 从不是创建控件“button12”的线程访问它”。我是新手,所以请各位大神尽量说得浅显一些。
  • Windows 窗体控件进行线程安全调用

    千次阅读 2007-10-31 11:54:00
    Windows窗体控件进行线程安全调用 [caven 发表于 2006-4-8 16:49:32]
  • 线程处理可以提高 Windows 窗体应用的性能,但对 Windows 窗体控件的访问本质上不是线程安全的。 多线程处理可以将你的代码公开给非常严重且复杂的 bug。 处理控件的两个或多个线程可能会强制控件处于不一致状态并...
  • 今天在编写c#的windows窗体程序的时候,需要用到线程来控制一个
  • Windows窗体上假设有一个TextBox控件,我们需要在另外的线程中改变它的Text值。  错误的代码(非安全线程访问)如下:  private Thread demoThread = null;  //在按钮单击事件中新建线程  private void ...
  • 理解windows 窗体和wpf的跨线程调用

    千次阅读 2014-03-17 18:35:53
    你曾开发过Windows窗体程序,可能会注意到有时事件处理程序将抛出InvalidOperationException异常,信息为“跨线程调用非法:在非创建控件的线程上访问该控件”。这种Windows窗体应用程序中跨线程调用时的一个最为
  • 如果使用多线程来提高 Windows 窗体应用程序的性能,则必须确保以线程安全方式调用控件。 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致...
  • 本文讲解如何使用多线程安全地使用.NET 中的Windows窗体控件。  使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。  访问 Windows 窗体控件本质上不是线程安全的。如果有两个...
  • 使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。 示例 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 27,471
精华内容 10,988
关键字:

windows窗体线程是什么