从SynchonizationContext派生

时间:2010-10-15 17:15:10

标签: c# .net multithreading

简而言之,我实现了一个派生自SynchronizationContext的类,使GUI应用程序可以轻松使用在GUI线程以外的线程上引发的事件。我非常感谢对我的实施的评论。具体来说,你有什么建议反对或可能导致我没有预料到的问题吗?我的初步测试已经成功。

长版: 我目前正在开发分布式系统(WCF)的业务层,它使用回调将事件从服务器传播到客户端。我的设计目标之一是提供可绑定的业务对象(即INotifyPropertyChanged / IEditableObject等),以便在客户端轻松使用这些对象。作为其中的一部分,我提供了一个回调接口的实现,它在事件进入时处理事件,更新业务对象,进而引发属性更改事件。因此,我需要在GUI线程上引发这些事件(以避免跨线程操作异常)。因此,我尝试提供自定义SynchronizationContext,实现回调接口的类使用它来将事件传播到GUI线程。另外,我希望这种实现独立于客户端环境 - 例如一个WinForms GUI应用程序或ConsoleApp或其他东西。换句话说,我不想假设静态SynchronizationContext.Current可用。因此我使用ExecutionContext作为后备策略。

public class ImplicitSynchronisationContext : SynchronizationContext

{

private readonly ExecutionContext m_ExecContext;
private readonly SynchronizationContext m_SyncContext;


public ImplicitSynchronisationContext()
{
    // Default to the current sync context if available.
    if (SynchronizationContext.Current != null)
    {
        m_SyncContext = SynchronizationContext.Current;
    }
    else
    {
        m_ExecContext = ExecutionContext.Capture();
    }
}


public override void Post(SendOrPostCallback d, object state)
{
    if (m_SyncContext != null)
    {
        m_SyncContext.Post(d, state);
    }
    else
    {
        ExecutionContext.Run(
            m_ExecContext.CreateCopy(),
            (object args) =>
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(this.Invoker), args);
            },
            new object[] { d, state });
    }
}
public override void Send(SendOrPostCallback d, object state)
{
    if (m_SyncContext != null)
    {
        m_SyncContext.Send(d, state);
    }
    else
    {
        ExecutionContext.Run(
            m_ExecContext.CreateCopy(),
            new ContextCallback(this.Invoker),
            new object[] { d, state });
    }
}


private void Invoker(object args)
{
    Debug.Assert(args != null);
    Debug.Assert(args is object[]);

    object[] parts = (object[])args;

    Debug.Assert(parts.Length == 2);
    Debug.Assert(parts[0] is SendOrPostCallback);

    SendOrPostCallback d = (parts[0] as SendOrPostCallback);

    d(parts[1]);
}

}

4 个答案:

答案 0 :(得分:2)

不幸的是你写了一些已经存在的东西。 SynchronizationContext类完全按照您的方式执行。将属性添加到主类,类似于:

    public static SynchronizationContext SynchronizationContext {
        get {
            if (SynchronizationContext.Current == null) {
                SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
            }
            return SynchronizationContext.Current;
        }
    }

或者使用AsyncOperationManager.SynchronizationContext,它完全相同。当然是优选的。

答案 1 :(得分:1)

我认为上面的代码在技术上没有任何问题..

然而,它比实际需要更复杂。没有真正的理由复制ExecutionContext并在其中运行操作。这通过调用ThreadPool.QueueUserWorkItem自动发生。有关详细信息,请参阅ExecutionContext的文档:

  

在应用程序域中,只要传输线程,就必须传输整个执行上下文。在Thread.Start方法,大多数线程池操作和Windows窗体线程通过Windows消息泵进行传输期间发生这种情况。

就个人而言,我会放弃跟踪ExecutionContext,除非确实需要它,并将其简化为:

public class ImplicitSynchronisationContext : SynchronizationContext
{
    private readonly SynchronizationContext m_SyncContext;

    public ImplicitSynchronisationContext()
    {
        // Default to the current sync context if available.
        m_SyncContext = SynchronizationContext.Current;
    }


    public override void Post(SendOrPostCallback d, object state)
    {
        if (m_SyncContext != null)
        {
            m_SyncContext.Post(d, state);
        }
        else
        {
            ThreadPool.QueueUserWorkItem(_ => d(state));
        }
    }

    public override void Send(SendOrPostCallback d, object state)
    {
        if (m_SyncContext != null)
        {
            m_SyncContext.Send(d, state);
        }
        else
        {
            d(state);
        }
    }
}

答案 2 :(得分:0)

我对你写这门课的动机有点不确定。

如果您使用的是WinForms或WPF,它们会提供使用SynchronizationContext.Current提供的实现。

如果您在控制台应用程序中,则控制主线程。你是如何与这个线程沟通的?

如果您正在运行Windows消息循环,可能是您正在使用WinForms或WPF。

如果您正在等待生产者/消费者队列,您的主线程将是消费事件的线程,因此根据定义,您将在主线程上。

尼克

答案 3 :(得分:0)

非常感谢大家的反馈。

Hans Passant的回答促使我发展/改变我的解决方案。

回顾一下,我的问题主要是如何从我的WCF服务获取异步回调以传播到客户端的UI线程(WinForms或WPF),而不需要客户端开发人员的任何工作。

我放弃了上面提供的实施,因为它是多余的。我的服务回调契约的实现现在只有一个重载的构造函数,它接受bool synchroniseCallbacks。如果是,我存储对AsyncOperationManager.SynchronizationContext的引用。当事件从我的服务进入时,我使用该同步上下文发布或发送它们。

正如Hans指出的那样,使用AsyncOperationManager公开的同步上下文的好处是它永远不会为null,而且,在WinForms和WPF等GUI应用程序中,它将返回UI线程的同步上下文 - 问题解决了!

干杯!