构建一个在特定或 UI 线程中引发事件的类






4.57/5 (6投票s)
C# 和 VB.Net 中的 BackgroundWorkerThreadSafe 类
- Demo 下载 VS2008 VB.Net noexe - 23.5 KB
- Demo 下载 VS2008 VB.Net - 192.4 KB
- Demo 下载 VS2008 CSharp noexe - 16.9 KB
- Demo 下载 VS2008 CSharp - 66.6 KB
引言
这不是一个关于线程或线程安全性的教程;网上已经有很多这类教程了。如果你从未用过 Invoke,或者不了解线程上下文或同步,请先在网上搜索“避免 InvokeRequired”。有一篇 Pablo Grisafi 撰写的精彩 CodeProject 文章,可以帮助你快速入门。你也可以搜索“ISynchronizeInvoke”或“Synchronization Context”,你会找到无数的文章来为你提供教育。你无法在任何网络搜索中找到本文介绍的代码(直到一些开发者从这里获取并传播出去)。
术语
如果你不是初学者,请跳过本节。或者,如果你知识渊博但喜欢找乐子,也可以阅读一下。
我父亲过去常对我说“读本书”,因为他可以用一个简短的回答为你节省时间。所以,本着他当时惹恼我的精神;我将尝试为初学者提供一个非常简化的概述,并希望没有傲慢的熟练人士为了显得自己很聪明而通过指出疏忽和因简洁性导致的错误来发表评论。
当你编写源代码时,你很可能会使用 IDE(集成开发环境)。一个例子是 Visual Studio 2012。然而,许多开发者可能选择不使用 IDE,而是从命令行编译源代码。他们可能有自己开发的定制源代码编辑器和编译器工具,以节省时间和按键次数。IDE 会帮助你编写和管理源代码,并提供编译源代码的便捷方式。以 Visual Studio 2012 为例,其中包含多个编译器。最常用的托管代码编译器是 C#,也称为 CSharp。另一个流行的托管语言是 VB.Net,也称为 Visual Basic dot Net。
当你创建一个简单的 Windows 应用程序(程序)时,你会创建一个供人们使用的窗体。这与创建用户可以交互的窗体是同一个意思。窗体也称为用户界面。注意我们使用的术语是多么的合理;只需稍加智能推断,你就可以看到用户界面是程序的一部分,供用户使用。或者换句话说;用户与之交互的部分。就像现实生活中一样;事情永远不是那么简单。一个程序可能包含多个用户界面。在现代面向对象编程(OOP)中,记住继承非常重要。一个程序可能包含多个用户界面,但是每个用户界面都会实例化它自己的对象。实例化意味着创建。通过调用对象的构造函数来创建对象。在 VB.Net 中,每个构造函数都是“Sub New”,而在 C# 中,它是一个与类同名的子例程。注意:在 VB.Net 和 C# 等托管语言中,创建或调用析构函数是不必要的,也是不需要的。一旦一个对象被实例化(创建),你就拥有了该对象的一个实例。我们编写的类的源代码是我们对象的“元数据”。只有运行时的一个操作系统线程才能为我们创建(实例化)我们的对象(类),此时我们的“实例”才有一个“上下文”。在应用程序中存在多个线程(后台工作线程等)的情况下,我们必须确保一个线程的实例不会调用另一个线程的实例的方法。这也被称为不是线程安全的。当一个后台线程引发一个事件(也称为触发一个事件,也称为调用一个方法)时,我们可能希望通过简单地“调用事件,以便实例化其对象实例的线程是执行被调用方法的线程”来避免跨线程问题。
这是我在这个主题上能给予你的最大帮助,除非动手术将知识直接植入你的大脑。
背景
当我第一次发现 BackgroundWorker
类时,我想:“太好了,当我需要实现一些后台处理时,有一个方便的方法可以发送和遗忘。”虽然可以假定由 DoWork
事件处理的方法会在一个单独的线程中运行,并且本质上不是线程安全的。我曾假设 ProgressChanged
事件对我的主线程来说是线程安全的。我的意思是,如果不是这样,为什么还要创建这样一个类呢?结果发现事实并非如此!
问题在于,由 ProgressChanged
事件调用的方法有时不会在实例化该方法对象实例的线程中执行。
解决方案是通过显式指定线程来引发事件,或者自动检测 UIThread 并指定该方法在该线程上执行。因此,我创建了一个类,这个类比我认为 BackgroundWorker
最初应该具备的功能更直观。
跨线程并不美观,也不总是容易避免。如果你是初学者;只需使用 BackgroundWorkerThreadSafe
类,而无需深入研究代码。如果你是中级或高级开发者;请深入研究并进行修改;我们随时欢迎你的建议。在如何引发事件同时保持线程安全方面,有很多可以提出建议的想法。
使用代码
BackgroundWorkerThreadSafe
是一个简短简单的类。它继承了 BackgroundWorker
,重写了 OnProgressChanged
和 OnRunWorkerCompleted
,并通过一个共享方法 RaiseEventAndExecuteItInAnExplicitOrUIThread
引发这些事件;这是这里讨论的核心代码。
对于 VB.Net 开发者,类定义如下:
Public Class BackgroundWorkerThreadSafe
'System.Windows.Threading.Dispatcher may require you
'to Add a Reference to "WindowsBase" (yes that is a Dot.Net library)
Inherits System.ComponentModel.BackgroundWorker
Public Shadows Event ProgressChanged(ByVal sender As Object, _
ByVal e As System.ComponentModel.ProgressChangedEventArgs)
Public Shadows Event RunWorkerCompleted(ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
Private m_ExplicitDispatcher As System.Windows.Threading.Dispatcher = Nothing
Protected Overrides Sub OnProgressChanged(ByVal e As System.ComponentModel.ProgressChangedEventArgs)
RaiseEventAndExecuteItInAnExplicitOrUIThread(ProgressChangedEvent, New Object() {Me, e}, m_ExplicitDispatcher)
End Sub
Protected Overrides Sub OnRunWorkerCompleted(ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
RaiseEventAndExecuteItInAnExplicitOrUIThread(RunWorkerCompletedEvent, New Object() {Me, e}, m_ExplicitDispatcher)
End Sub
Public Sub New(ByVal ExplicitDispatcher As System.Windows.Threading.Dispatcher)
MyBase.New()
m_ExplicitDispatcher = ExplicitDispatcher
End Sub
Private Shared Sub RaiseEventAndExecuteItInAnExplicitOrUIThread(ByVal _event _
As System.MulticastDelegate, ByVal _ParamArray_args() As Object, _
ByVal ExplicitThreadSynchronizationDispatcher As System.Windows.Threading.Dispatcher)
If Not _event Is Nothing Then
If _event.GetInvocationList().Length > 0 Then
Dim _sync As System.ComponentModel.ISynchronizeInvoke = Nothing
For Each _delegate As System.MulticastDelegate In _event.GetInvocationList()
If ((ExplicitThreadSynchronizationDispatcher Is Nothing) AndAlso (_sync Is Nothing) _
AndAlso (GetType(System.ComponentModel.ISynchronizeInvoke).IsAssignableFrom(_
_delegate.Target.GetType())) AndAlso (Not _delegate.Target.GetType().IsAbstract)) Then
Try
_sync = CType(_delegate.Target, System.ComponentModel.ISynchronizeInvoke)
Catch ex As Exception
Diagnostics.Debug.WriteLine(ex.ToString())
_sync = Nothing
End Try
End If
If Not ExplicitThreadSynchronizationDispatcher Is Nothing Then
Try
ExplicitThreadSynchronizationDispatcher.Invoke(_delegate, _ParamArray_args)
Catch ex As Exception
Diagnostics.Debug.WriteLine(ex.ToString())
End Try
Else
If _sync Is Nothing Then
Try
_delegate.DynamicInvoke(_ParamArray_args)
Catch ex As Exception
Diagnostics.Debug.WriteLine(ex.ToString())
End Try
Else
Try
_sync.Invoke(_delegate, _ParamArray_args)
Catch ex As Exception
Diagnostics.Debug.WriteLine(ex.ToString())
End Try
End If
End If
Next
End If
End If
End Sub
End Class
对于 C# 开发者,类定义如下:
public class BackgroundWorkerThreadSafe : System.ComponentModel.BackgroundWorker
//System.Windows.Threading.Dispatcher may require you to
//Add a Reference to "WindowsBase" (yes that is a Dot.Net library)
{
public new event ProgressChangedEventHandler ProgressChanged;
public delegate void ProgressChangedEventHandler(object sender, System.ComponentModel.ProgressChangedEventArgs e);
public new event RunWorkerCompletedEventHandler RunWorkerCompleted;
public delegate void RunWorkerCompletedEventHandler(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e);
private System.Windows.Threading.Dispatcher m_ExplicitDispatcher = null;
protected override void OnProgressChanged(System.ComponentModel.ProgressChangedEventArgs e)
{
RaiseEventAndExecuteItInAnExplicitOrUIThread(ProgressChanged, new object[] {
this,
e
}, m_ExplicitDispatcher);
}
protected override void OnRunWorkerCompleted(System.ComponentModel.RunWorkerCompletedEventArgs e)
{
RaiseEventAndExecuteItInAnExplicitOrUIThread(RunWorkerCompleted, new object[] {
this,
e
}, m_ExplicitDispatcher);
}
public BackgroundWorkerThreadSafe(System.Windows.Threading.Dispatcher ExplicitDispatcher)
: base()
{
m_ExplicitDispatcher = ExplicitDispatcher;
}
private static void RaiseEventAndExecuteItInAnExplicitOrUIThread(System.MulticastDelegate
_event, object[] _ParamArray_args, System.Windows.Threading.Dispatcher ExplicitThreadSynchronizationDispatcher)
{
if ((_event != null))
{
if (_event.GetInvocationList().Length > 0)
{
System.ComponentModel.ISynchronizeInvoke _sync = null;
foreach (System.MulticastDelegate _delegate in _event.GetInvocationList())
{
if (((ExplicitThreadSynchronizationDispatcher == null) && (_sync == null) &&
(typeof(System.ComponentModel.ISynchronizeInvoke).IsAssignableFrom(_delegate.Target.GetType())) &&
(!_delegate.Target.GetType().IsAbstract)))
{
try
{
_sync = (System.ComponentModel.ISynchronizeInvoke)_delegate.Target;
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
_sync = null;
}
}
if ((ExplicitThreadSynchronizationDispatcher != null))
{
try
{
ExplicitThreadSynchronizationDispatcher.Invoke(_delegate, _ParamArray_args);
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
else
{
if (_sync == null)
{
try
{
_delegate.DynamicInvoke(_ParamArray_args);
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
else
{
try
{
_sync.Invoke(_delegate, _ParamArray_args);
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
}
}
}
}
}
}
正如我所说,网上有太多的教程和参考资料,我不需要在此写重复的信息。你在搜索中找不到的是本文提到的共享方法。
随附的 VB.NET 和 C# 演示项目都是简单的 Windows 应用程序,展示了如何使用该类的完整示例。示例与原始 BackGroundWorker
相同,只是构造函数接受一个参数,该参数可以是 Nothing 或 null;或者它可以是 Systems.Windows.Threading.Dispatcher
,用于显式设置由事件处理的方法将在哪个线程内执行。如果参数是 Nothing 或 null,则将使用 System.ComponentModel.ISynchronizeInvoke
委托目标来确定实例化 BackgroundworkerThreadSafe
的线程,并且如果没有找到接口,它将使用委托的 DynamicInvoke 方法正常引发事件。
关注点
由于该代码使用了 Systems.Windows.Threading.Dispatcher
,因此有必要添加对 WindowsBase
的引用。如果有人知道如何利用 System.Threading.SynchronizationContext
来替换此功能,请告知我们。
历史
读者可以看到,代码的原始大小并不庞大。为什么微软没有在原始类中集成此功能或类似功能,这超出了常理。无论如何;请尽情享用并发表评论!