简体   繁体   English

C#等待异步方法清除SynchronizationContext(带有repro测试)

[英]C# await in async method clears SynchronizationContext (with repro tests)

Using Visual Studio 2015 Update 3 and a C# Test project targeting .NET 4.6.1 I get the following behavior: 使用Visual Studio 2015 Update 3和针对.NET 4.6.1的C#测试项目,我得到以下行为:

[TestClass]
public class AwaitTests
{
    [TestMethod]
    public void AsyncRemovingSyncContext_PartialFail()
    {
        Log("1");
        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
        Log("2");
        HasAwait().Wait(); // The continuation in this method is on wrong thread
        Log("5");
        Assert.IsNotNull(SynchronizationContext.Current);
    }

    [TestMethod]
    public async Task AsyncRemovingSyncContext_Fail()
    {
        Log("1");
        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
        Log("2");
        await HasAwait();
        Log("5"); // Issue is here - Sync Context is now null
        Assert.IsNotNull(SynchronizationContext.Current);
    }

    public async Task HasAwait()
    {
        Log("3");
        await Task.Delay(300);
        Log("4");
    }

    private void Log(string text)
    {
        Console.WriteLine($"{text} - Thread {System.Threading.Thread.CurrentThread.ManagedThreadId} - {SynchronizationContext.Current}");
    }
}

Here is the output: 这是输出:

AsyncRemovingSyncContext_PartialFail AsyncRemovingSyncContext_PartialFail
1 - Thread 7 - 1-线程7-
2 - Thread 7 - System.Threading.SynchronizationContext 2-线程7-System.Threading.SynchronizationContext
3 - Thread 7 - System.Threading.SynchronizationContext 3-线程7-System.Threading.SynchronizationContext
4 - Thread 8 - 4-线8-
5 - Thread 7 - System.Threading.SynchronizationContext 5-线程7-System.Threading.SynchronizationContext

AsyncRemovingSyncContext_Error AsyncRemovingSyncContext_Error
1 - Thread 7 - 1-线程7-
2 - Thread 7 - System.Threading.SynchronizationContext 2-线程7-System.Threading.SynchronizationContext
3 - Thread 7 - System.Threading.SynchronizationContext 3-线程7-System.Threading.SynchronizationContext
4 - Thread 8 - 4-线8-
5 - Thread 8 - 5-线8-
-Assert Exception Thrown- -声明抛出异常-

I've done other tests and so far there is a 100% correlation between the existence of the await keyword in the method and the clearing of the Sync Context. 我已经做过其他测试,到目前为止,该方法中await关键字的存在与Sync Context的清除之间存在100%的相关性。 This includes async lambdas. 这包括异步lambda。

This is important to me because it appears that as soon as an await is encountered, the sync context is removed. 这对我很重要,因为看起来一旦遇到await ,同步上下文就会被删除。 This means that if I perform two await s in the same method, the second continuation will just run in the thread pool (default behavior when no Sync Context). 这意味着,如果我以相同的方法执行两个await ,则第二个继续操作将仅在线程池中运行(没有Sync Context时的默认行为)。

Is this a framework/compiler bug or am I doing something wrong? 这是框架/编译器错误,还是我做错了什么?


For specifics, since I'm sure someone will ask in one form or another, I have an Active Object that I would like to enable async \\ await support for, but I can only do that if I can guarentee that the continuation will be dispatched to my ActiveObjectSynchronizationContext , which at the moment, it's not because it's being cleared. 具体来说,因为我确定有人会以一种或另一种形式询问,所以我有一个Active Object ,我想启用async \\ await支持,但是只有在我可以确保继续执行将被调度的情况下,我才能这样做。到我的ActiveObjectSynchronizationContext ,目前不是因为它已被清除。

I've already looked at this question (and similar about the UI context 4.0 bug) but this isn't related since I'm running 4.6.1 and I'm using non-UI threads. 我已经看过这个问题了 (与UI上下文4.0错误类似),但这无关紧要,因为我正在运行4.6.1,并且我使用的是非UI线程。

I also followed the advice from this other question and ensured my Sync Context implements CreateCopy , but I can tell from test profiling that the method isn't even called. 我还遵循了另一个问题的建议,并确保我的Sync Context实现了CreateCopy ,但是从测试概要文件中我可以知道甚至没有调用该方法。

new SynchronizationContext() by convention is the same as a null synchronization context; 按照约定, new SynchronizationContext()null同步上下文相同; that is, the thread pool context. 即线程池上下文。 So, the behavior in those tests is not unexpected. 因此,这些测试中的行为并不意外。

await is behaving correctly; await状态正确; it is seeing the current synchronization context and Post ing its continuation to that context. 它是看到当前同步上下文和Post荷兰国际集团及其延续这方面。 However, the thread pool sync context just executes the delegate on a thread pool thread - it doesn't set SynchronizationContext.Current to new SynchronizationContext because the convention is that null is the thread pool sync context. 但是,线程池同步方面只执行一个线程池线程的委托-它不设置SynchronizationContext.Currentnew SynchronizationContext ,因为约定是null 线程池同步上下文。

I believe the problem with your real code is that your ActiveObjectSynchronizationContext is not setting itself as current when executing queued delegates. 我相信您的实际代码存在的问题是,在执行排队的委托时,您的ActiveObjectSynchronizationContext没有将自身设置为当前的。 It's the responsibility of SynchronizationContext.Post to call SetSynchronizationContext if necessary just before executing the delegate, although - correct me if I'm wrong - the name "Active Object" seems to imply a single-threaded STA-like model. SynchronizationContext.Post有责任在执行委托之前在必要时调用SetSynchronizationContext ,但是-如果我错了,请纠正我-名称“ Active Object”似乎暗示着一个类似于单线程STA的模型。 If this is the case, then Post wouldn't need to set it; 如果是这种情况,则Post无需进行设置; it would just need to execute the delegate on the correct thread, which should already have the correct current sync context. 它只需要在正确的线程上执行委托,该线程应该已经具有正确的当前同步上下文。

If it helps, I have a single-threaded SynchronizationContext here , though it doesn't do any kind of (explicit) message pumping. 如果有帮助,我有一个单线程SynchronizationContext 在这里 ,但它不会做任何形式的(明确的)消息抽水。

As @StephenCleary mentioned in a comment, the issue was that in my real code I was setting the Sync Context in an async method but before the await keyword, which I thought would work but apparently it doesn't (I'm guessing the state machine grabs the context at the start of the method?). 正如@StephenCleary在评论中提到的那样,问题在于,在我的真实代码中,我在async方法中但在await关键字之前设置了Sync Context,我认为这是可行的,但显然没有用(我猜测状态机器在方法开始时获取上下文?)。

The solution is to set the Sync Context in a non-async method first. 解决方案是首先以非异步方法设置同步上下文。

Wrong 错误

activeObject.Enqueue(async () =>
    {
        SynchronizationContext
            .SetSynchronizationContext(
                new ActiveObjectSynchronizationContext(activeObject));

        // do work

        await Task.Delay(500);

        // Sync Context now cleared
    });

Right

activeObject.Enqueue(() =>
        SynchronizationContext
            .SetSynchronizationContext(
                new ActiveObjectSynchronizationContext(activeObject));
    );

activeObject.Enqueue(async () =>
    {
        // do work

        await Task.Delay(500);

        // Still on Active Object thread
    });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM