C#对Windows窗体控件进行线程安全调用_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > C#对Windows窗体控件进行线程安全调用

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

 2013/7/27 18:19:19  阿凡卢  博客园  我要评论(0)
  • 摘要:如果使用多线程来提高Windows窗体应用程序的性能,则必须确保以线程安全方式调用控件。访问Windows窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能会出现其他与线程相关的Bug,例如争用情况和死锁。确保以线程安全方式访问控件非常重要。在未使用Invoke方法的情况下,从不是创建某个控件的线程的其他线程调用该控件是不安全的。以下非线程安全的调用的示例
  • 标签:Windows C# 控件 线程安全 线程

如果使用多线程来提高 Windows 窗体应用程序的性能,则必须确保以线程安全方式调用控件。

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

在未使用 Invoke 方法的情况下,从不是创建某个控件的线程的其他线程调用该控件是不安全的。 以下非线程安全的调用的示例。

// This event handler creates a thread that calls a 
// Windows Forms control in an unsafe way.
private void setTextUnsafeBtn_Click(object sender, EventArgs e)
{
    this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe));
    this.demoThread.Start();
}

// This method is executed on the worker thread and makes
// an unsafe call on the TextBox control.
private void ThreadProcUnsafe()
{
    this.textBox1.Text = "This text was set unsafely.";
}

.NET Framework 可帮助您检测以非线程安全方式访问控件这一问题。 在调试器中运行应用程序时,如果一个不是创建某个控件的线程的其他线程调用该控件,则调试器会引发一个 InvalidOperationException,并显示以下消息:“从不是创建控件控件名称 的线程访问它。”

异常在调试期间和运行时的某些情况下可靠地发生。 在调试以 .NET Framework 2.0之前的 .NET Framework 编写的应用程序时,可能会出现此异常。 我们强烈建议您在发现此问题时进行修复,但您可以通过将CheckForIllegalCrossThreadCalls 属性设置为 false 来禁用它。 这会使控件像在 Visual Studio .NET 2003 和 .NET Framework 1.1 中一样运行。

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

  1. 查询控件的 InvokeRequired 属性。
  2. 如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。
  3. 如果 InvokeRequired 返回 false,则直接调用控件。

在下面的代码示例中,将在由后台线程执行的 ThreadProcSafe 方法中实现线程安全调用。 如果 TextBox控件的 InvokeRequired 返回 true,则 ThreadProcSafe 方法会创建 SetTextCallback 的一个实例,并将该实例传递给窗体的 Invoke 方法。 这使得 SetText 方法被创建 TextBox 控件的线程调用,而且在此线程上下文中将直接设置 Text 属性。

// This event handler creates a thread that calls a 
// Windows Forms control in a thread-safe way.
private void setTextSafeBtn_Click(object sender, EventArgs e)
{
    this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
    this.demoThread.Start();
}

// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
private void ThreadProcSafe()
{
    this.SetText("This text was set safely.");
}

// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control. 
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly. 

private void SetText(string text)
{
    // InvokeRequired required compares the thread ID of the
    // calling thread to the thread ID of the creating thread.
    // If these threads are different, it returns true.
    if (this.textBox1.InvokeRequired)
    {    
        SetTextCallback d = new SetTextCallback(SetText);
        this.Invoke(d, new object[] { text });
    }
    else
    {   
        this.textBox1.Text = text;
    }
}

使用 BackgroundWorker 进行线程安全调用

在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。 BackgroundWorker 组件使用事件驱动模型实现多线程。 后台线程运行 DoWork 事件处理程序,而创建控件的线程运行 ProgressChanged 和RunWorkerCompleted 事件处理程序。 可以从 ProgressChanged 和 RunWorkerCompleted 事件处理程序调用控件。

使用 BackgroundWorker 进行线程安全调用

  1. 创建一个方法,该方法用于执行您希望在后台线程中完成的工作。 不要调用由此方法中的主线程创建的控件。
  2. 创建一个方法,用于在后台工作完成后报告结果。 在此方法中可以调用主线程创建的控件。
  3. 将步骤 1 中创建的方法绑定到 BackgroundWorker 的实例的 DoWork 事件,并将步骤 2 中创建的方法绑定到同一实例的 RunWorkerCompleted 事件。
  4. 若要启动后台线程,请调用 BackgroundWorker 实例的 RunWorkerAsync 方法。

在下面的代码示例中,DoWork 事件处理程序使用 Sleep 来模拟需要花费一些时间完成的工作。 它不调用窗体的 TextBox 控件。 TextBox 控件的 Text 属性在 RunWorkerCompleted 事件处理程序中直接设置。

// This BackgroundWorker is used to demonstrate the 
// preferred way of performing asynchronous operations.
private BackgroundWorker backgroundWorker1;

// This event handler starts the form's 
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
{
    this.backgroundWorker1.RunWorkerAsync();
}
        
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the 
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous operations.

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.textBox1.Text = "This text was set safely by BackgroundWorker.";
}

也可以使用 ProgressChanged 事件来报告后台任务的进度。 有关包含该事件的示例,请参见BackgroundWorker。

虽然多线程处理最适于运行过程和类方法,它也可以用于窗体和控件。使用时,请注意以下几点:

  • 只要有可能,便仅在用来创建它的线程上执行控件的各种方法。若须从另一线程调用控件的方法,则必须使用 Invoke 来调用该方法。
  • 不要使用 SyncLock 语句锁定操作控件或窗体的线程。由于控件和窗体的方法有时回调到调用过程,因此可能会因无意中创建了死锁而终止运行(死锁是指两个线程都等待对方释放锁定,从而导致应用程序暂停的情况)。

参考:

http://msdn.microsoft.com/zh-cn/library/ms171728(v=vs.100).aspx

 

 

发表评论
用户名: 匿名