为什么SynchronizationContext无法正常工作?

时间:2010-10-08 16:24:09

标签: .net multithreading asynchronous synchronizationcontext

我有以下代码:

[TestMethod]
public void StartWorkInFirstThread()
{
    if (SynchronizationContext.Current == null)
        SynchronizationContext.SetSynchronizationContext(
            new SynchronizationContext());

    var syncContext = SynchronizationContext.Current;

    Console.WriteLine("Start work in the first thread ({0})", 
        Thread.CurrentThread.ManagedThreadId);

    var action = ((Action) DoSomethingInSecondThread);
    action.BeginInvoke(CallbackInSecondThread, syncContext);

    // Continue its own work
}

private static void DoSomethingInSecondThread()
{
    Console.WriteLine("Do something in the second thread ({0})", 
        Thread.CurrentThread.ManagedThreadId);   
}

private void CallbackInSecondThread(IAsyncResult ar)
{
    Console.WriteLine("Callback in the second thread ({0})", 
        Thread.CurrentThread.ManagedThreadId);
    var syncContext = (SynchronizationContext) ar.AsyncState;
    syncContext.Post(CallbackInFirstThread, null);
}

private void CallbackInFirstThread(object obj)
{
    Console.WriteLine("Callback in the first thread ({0})",
        Thread.CurrentThread.ManagedThreadId);
}

我希望在第一个线程中执行最后一个方法,即从中获取SynchronizationContext的初始线程,因为我调用了此上下文的Post()方法。即像这样的东西:

Start work in the first thread (28)
Do something in the second thread (17)
Callback in the second thread (17)
Callback in the first thread (28)

是不是SynchronizationContext的含义?但实际上我有以下输出:

Start work in the first thread (28)
Do something in the second thread (17)
Callback in the second thread (17)
Callback in the first thread (7)

有什么问题? SynchronizationContext出了什么问题,或者我有一些误解?

更新:我使用Resharper测试运行器将此方法称为单元测试。

3 个答案:

答案 0 :(得分:8)

请参阅http://www.codeproject.com/KB/threads/SynchronizationContext.aspx

你需要的答案。您必须覆盖SynchronizationContext才能正确处理您的操作。

从以下开始阅读:

  

请注意,DoWork已执行   线程11,与Run1相同的线程。   没有太多的SynchronizationContext   进入主线程。为什么?什么是   继续?嗯......这是其中的一部分   当你意识到什么都没有   生活自由。线程不能只是   他们之间切换上下文   必须有内置的基础设施   进入他们是为了这样做。用户界面   例如,线程使用消息   泵,并在其内   SynchronizationContext,它利用   消息泵同步到UI   线程。

答案 1 :(得分:5)

SynchronizationContext的默认实现只是在调用线程中执行传递的委托(在调用Send / Post方法的线程中,而不是捕获上下文的线程)。如果您需要某些特定行为,例如某些操作的线程关联,则应手动实现。 BCL包含一些简化UI互操作性的现成实现,如WindowsFormsSynchronizationContextDispatcherSynchronizationContext

答案 2 :(得分:3)

您的期望是错误的,因为没有通用的方法将委托“注入”正在运行的线程。你的“第一个线程”在测试运行器中启动,将执行一个或多个测试,然后停止 - 没有办法打断它并告诉它运行CallbackInFirstThreadSynchronizationContext类在线程池中运行Post - ed委托,因为这是它唯一的选项。

WindowsFormsSynchronizationContext这样的派生类利用WinForms应用程序中的消息循环将Post ed委托传递给UI线程,但在测试运行器中没有等效的。

如果要检查您正在测试的代码所使用的SynchronizationContext,您可以创建自己的派生类,设置可在测试中检查的标记。这是一个例子:

public class TestSynchronizationContext : SynchronizationContext
{
    [ThreadStatic]
    private static object _CurrentPostToken;
    /// <summary>
    /// Gets the context's token, if the current thread is executing a delegate that
    /// was posted to this context; otherwise, null.
    /// </summary>
    public static object CurrentPostToken
    {
        get
        {
            return _CurrentPostToken;
        }
    }

    public object Token { get; private set; }

    /// <summary>
    /// Gets a WaitHandle that is set after the context executes a posted delegate.
    /// </summary>
    public AutoResetEvent PostHandle { get; private set; }

    public TestSynchronizationContext(object token)
    {
        Token = token;
        PostHandle = new AutoResetEvent(false);
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        try
        {
            _CurrentPostToken = Token;
            // Execute the callback on this thread, so that we can reset the context
            // when it's finished.
            d(state);
        }
        finally
        {
            _CurrentPostToken = null;
        }

        // The test method will wait on this handle so that it doesn't exit before
        // the synchronization context is called.
        PostHandle.Set();
    }
}

StartWorkInFirstThread中,将上下文设置为TestSynchronizationContext的实例:

SynchronizationContext.SetSynchronizationContext(
        new TestSynchronizationContext(new object()));

致电BeginInvoke后,您需要在退出测试前等待Post发生,请致电:

((TestSynchronizationContext)SynchronizationContext.Current).PostHandle.WaitOne(1000);

CallbackInFirstThread中,您可以查看以下内容使用的上下文:

Assert.IsNotNull(TestSynchronizationContext.CurrentPostToken);

关键是没有简单的方法来实际回发到第一个线程,但是你可以检查是否正在使用正确的上下文,以便当你的代码在真实的应用程序中运行时,回调将在UI线程。