C#:线程间的同步(The .NET Framework's New SynchronizationContext Class)

本文翻译自Leslie Sanford的文章The .NET Framework's New SynchronizationContext Class。

1)Introduction

类SynchronizationContext属于.Net Framework中System.Threading命名空间,本文主要是提供一个用于线程间通讯的模板,使用WinForm来处理事件,其它或少量涉及System.ComponentModel。

2)Background

还是上一篇文中所讲的,改变控件状态只能在控件被创建的线程中实现,其它的线程是被禁止的。于此相对应的,其它线程产生的事件需通过Invoke或BeginInvoke方法来更新其它线程的控件。这两个方法都属于ISynchronizeInvoke接口,目的是利用一个委托,在与ISynchronizeInvoke运行的对象属于同一个线程上触发。

典型的,一个Form对其它线程产生的线程的反应可以表达为:

private void HandleSomeEvent(object sender, EventArgs e)
{
    if(InvokeRequired)
    {
        BeginInvoke(new EventHandler(HandleSomeEvent), sender, e);
    }
    else
    {
        // Event logic here.
    }
}

这个方法首先检查布尔型变量InvokeRequired(同属于ISynchronizeInvoke接口),如果为True,就调用BeginInvoke方法,并传递一个委托及参数用于处理对应的事件;如果为False,就按正常逻辑走。换句话说,就是别的线程需要调用本线程的Control时,InvokeRequired才为True。

注意到,传给BeginInvoke的委托代表与处理事件相同的方法。如果InvokeRequired为True,这会导致触发事件处理过程两次:第一次调用BeginInvoke;第二次变为False变成直接调用。

3)The SynchronizationContext Class to the Rescue

能否不用在Form中检查InvokeRequired的状态,答案是使用类SynchronizationContext(我的翻译:同步上下文)。

同步上下文提供了一个渠道能保证传递进来的委托都能够在同一个线程被触发,类似于接口ISynchronizeInvoke接口。ISynchronizeInvoke中有方法Invoke,BeginInvoke,在同步上下文中有对应的方法Send,Post;其中,Send方法是同步触发委托(这与Invoke类似),Post方法与BeginInvoke类似,触发异步委托。

Send和Post都带有两个参数:SendOrPostCallback表示委托方法;类型为Object的对象表示传递给委托的状态信息。

SynchronizationContext类有一个SynchronizationContext静态方法用于设置SynchronizationContext对象(有点拗口)。SynchronizationContext对象可以通过静态属性Current获取,这个属性表示你当前所处的任何线程。

当SynchronizationContext静态方法被调用时,会携带SynchronizationContext对象,同时跟踪这个对象和它所属于的线程,以确保在调用Current静态方法时会得到SynchronizationContext对象所设定的线程。

让我们回到Form部分。当Form被创建时,代表Form线程的SynchronizationContext对象被设置(实际是个继承类WindowsFormsSynchronizationContext),其它对象可以通过Current属性来取得Form的SynchronizationContext对象,以通过委托来触发、更新Form线程上的控件。通过这种方式,Form中无需像上面那样检查触发的来源。一句话,解析来源的过程从接受者改为发送者。

这种模式可以帮助我们实现线程间的自动通讯。一个线程使用SynchronizationContext可以实现与其他线程间的安全通讯。

在这里,我们需要知道:SynchronizationContext类自己并没有做什么事情,它并不是一个抽象类,可以看做一个基类,用于重载后提供更有意义的功能。下面的代码实现了Send和Post方法:

public virtual void Send(SendOrPostCallback d, Object state)
{
    d(state);
}

public virtual void Post(SendOrPostCallback d, Object state)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}

Send方法只是简单传递状态参数,援引回调委托。Post方法调用线程池的异步方法。所以,SynchronizationContext默认的动作并没有做什么特别的,它的魔力来自于派生类。

派生类WindowsFormsSynchronizationContext就是这样的。Form使用这个类表征自己的同步信息。作者猜测这个类只是对Form的ISynchronizeInvoke的简单包装,分别委托调用Send(Invoke)和Post(BegiinInvoke)而已。

4)Using the SynchronizationContext Class

下面是使用SynchronizationContext类的简单模板

using System;
using System.Threading;

namespace SynchronizationContextExample
{
    public class MySynchronizedClass
    {
        private Thread workerThread;

        private SynchronizationContext context;

        public event EventHandler SomethingHappened;

        public MySynchronizedClass()
        {
            // It's important to get the current SynchronizationContext
            // object here in the constructor. We want the 
            // SynchronizationContext object belonging to the thread in
            // which this object is being created.
            context = SynchronizationContext.Current;

            // It's possible that the current thread does not have a 
            // SynchronizationContext object; a SynchronizationContext
            // object has not been set for this thread.
            //
            // If so, we simplify things by creating a SynchronizationContext
            // object ourselves. However! There could be some problems with 
            // this approach. See the article for more details.
            if(context == null)
            {
                context = new SynchronizationContext();
            }

            workerThread = new Thread(new ThreadStart(DoWork));

            workerThread.Start();
        }

        private void DoWork()
        {
            context.Post(new SendOrPostCallback(delegate(object state)
            {
                EventHandler handler = SomethingHappened;

                if(handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }), null);
        }
    }
}

如果在Form中使用此类,当前的SynchronizationContext对象就会允许我们Post、send事件到Form线程。存在,当类的实例在一个工作者线程或别的线程中创建,SynchronizationContext.Current可能为空,也就是说Set无效。所以上面才有null的检查过程,如果为null的话,直接创建一个对象并使用其默认值。

在我们的类中最重要的是,无需知道Sending/Posting事件的对象;无需ISynchronizeInvoke接口来同步自己,使用Current属性就可以了。

注意:创建SynchronizationContext对象也可能比较危险。如上所说,当Current属性为null时,最好是当做错误处理,而不是直接创建一个默认的SynchronizationContext对象。

5)The AsyncOperation and AsyncOperationManager Classes

不用直接接触SynchronizationContext对象,也无需对null状态的检查,更加方便我们使用:

using System;
using System.Threading;
using System.ComponentModel;

namespace SynchronizationContextExample
{
    public class MySynchronizedClass
    {
        private Thread workerThread;

        private AsyncOperation operation;

        public event EventHandler SomethingHappened;

        public MySynchronizedClass()
        {
            operation = AsyncOperationManager.CreateOperation(null);

            workerThread = new Thread(new ThreadStart(DoWork));

            workerThread.Start();
        }
        
        private void DoWork()
        {
            operation.Post(new SendOrPostCallback(delegate(object state)
            {
                EventHandler handler = SomethingHappened;

                if(handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }), null);

            operation.OperationCompleted();
        }
    }
}

6)The BackgroundWorker Class

使用这个模式的实例是BackgroundWorker类,允许我们在后台运行相关的工作。当在Form中使用时,我们无需检查BackgroundWorker的事件是哪个线程发出的,这一切是由于使用了Form的SynchronizationContext对象。


小结:

感觉文章比较难懂,我是在翻译前一篇文章时,作者提到过着这篇文章,所以有想看看的想法。

上一篇文章中有提到过一个小实例:在Form中更新BackgroundWorker的状态,可以参考。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值