3

我有以下代码:

[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 测试运行程序的单元测试。

4

3 回答 3

8

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

有你需要的答案。您必须覆盖SynchronizationContext以使其正确处理您的操作。

从以下开始阅读:

请注意,DoWork 在线程 11 上执行,与 Run1 相同。没有太多的 SynchronizationContext 进入主线程。为什么?这是怎么回事?嗯……这是你意识到生活中没有什么是免费的。线程不能只是在它们之间切换上下文,它们必须有一个内置的基础设施才能这样做。例如,UI 线程使用消息泵,在其 SynchronizationContext 中,它利用消息泵同步到 UI 线程。

于 2010-10-08T17:05:49.230 回答
5

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

于 2010-10-08T17:01:32.390 回答
3

您的期望是错误的,因为没有通用的方法可以将委托“注入”到正在运行的线程中。您的“第一个线程”在测试运行程序中启动,将执行一个或多个测试,然后停止 - 无法中断它并告诉它运行CallbackInFirstThread。该类在线程池中SynchronizationContext运行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 线程中运行。

于 2013-01-03T17:32:43.303 回答