为什么 SynchronizationContext 不能正常工作?

发布于 2024-09-26 08:45:30 字数 1792 浏览 4 评论 0原文

我有以下代码:

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

I have following code:

[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);
}

I expect last method to be executed in the first thread, i.e. initial thread where SynchronizationContext is taken from, because I call Post() method of this context. I.e. something like this:

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)

Isn't it the meaning of SynchronizationContext? But actually I have following output:

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)

What is the problem? Does something go wrong with SynchronizationContext or I have some misunderstanding?

Update: I call this method as a unit test using Resharper test runner.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

会发光的星星闪亮亮i 2024-10-03 08:45:30

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

这就是您需要的答案。您必须重写 SynchronizationContext 才能使其正确处理您的操作。

阅读开始于:

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

See http://www.codeproject.com/KB/threads/SynchronizationContext.aspx

There is the answer you need. You must override SynchronizationContext to make it properly handling your operations.

Read starting from:

Notice that DoWork is executed on
thread 11, the same thread as Run1.
Not much of a SynchronizationContext
into the main thread. Why? What's
going on? Well... This is the part
when you realize that nothing is for
free in life. Threads can't just
switch contexts between them, they
must have an infrastructure built-in
into them in order to do so. The UI
thread, for example, uses a message
pump, and within its
SynchronizationContext, it leverages
the message pump to sync into the UI
thread.

请别遗忘我 2024-10-03 08:45:30

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

Default implementation of SynchronizationContext just executes passed delegate in the calling thread (in the thread that invokes Send/Post method not the thread that captures context). If you need some particular behavior, like thread affinity for some operations, you should implement this manually. BCL contains few out-of-box implementations for simplification of UI interoperability, like WindowsFormsSynchronizationContext or DispatcherSynchronizationContext.

满意归宿 2024-10-03 08:45:30

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

WindowsFormsSynchronizationContext 等派生类利用 WinForms 应用程序中的消息循环将 Post 委托传递给 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 线程中运行。

Your expectation is wrong because there's no general way to "inject" a delegate into a running thread. Your "first thread" was started in the test runner, will execute one or more tests, and will then stop - there's no way to interrupt it and tell it to run CallbackInFirstThread. The SynchronizationContext class runs Post-ed delegates in the thread pool because that's about the only option it has.

Derived classes like WindowsFormsSynchronizationContext make use of the message loop in WinForms applications to pass the Post-ed delegate to the UI thread, but there's no equivalent in a test runner.

If you want to check which SynchronizationContext the code you're testing is using, you could create your own derived class that sets a flag you can check in your test. Here's an example:

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();
    }
}

In StartWorkInFirstThread, set the context to an instance of TestSynchronizationContext:

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

After you call BeginInvoke, you need to wait for the Post to happen before you exit the test, so call:

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

In CallbackInFirstThread you can check what context is being used with something like:

Assert.IsNotNull(TestSynchronizationContext.CurrentPostToken);

The point is that there's no easy way to actually post back to the first thread, but you can check that the right context is being used so that, when your code runs in a real application, the callback will be running in the UI thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文