繁体   English   中英

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

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

使用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}");
    }
}

这是输出:

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

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

我已经做过其他测试,到目前为止,该方法中await关键字的存在与Sync Context的清除之间存在100%的相关性。 这包括异步lambda。

这对我很重要,因为看起来一旦遇到await ,同步上下文就会被删除。 这意味着,如果我以相同的方法执行两个await ,则第二个继续操作将仅在线程池中运行(没有Sync Context时的默认行为)。

这是框架/编译器错误,还是我做错了什么?


具体来说,因为我确定有人会以一种或另一种形式询问,所以我有一个Active Object ,我想启用async \\ await支持,但是只有在我可以确保继续执行将被调度的情况下,我才能这样做。到我的ActiveObjectSynchronizationContext ,目前不是因为它已被清除。

我已经看过这个问题了 (与UI上下文4.0错误类似),但这无关紧要,因为我正在运行4.6.1,并且我使用的是非UI线程。

我还遵循了另一个问题的建议,并确保我的Sync Context实现了CreateCopy ,但是从测试概要文件中我可以知道甚至没有调用该方法。

按照约定, new SynchronizationContext()null同步上下文相同; 即线程池上下文。 因此,这些测试中的行为并不意外。

await状态正确; 它是看到当前同步上下文和Post荷兰国际集团及其延续这方面。 但是,线程池同步方面只执行一个线程池线程的委托-它不设置SynchronizationContext.Currentnew SynchronizationContext ,因为约定是null 线程池同步上下文。

我相信您的实际代码存在的问题是,在执行排队的委托时,您的ActiveObjectSynchronizationContext没有将自身设置为当前的。 SynchronizationContext.Post有责任在执行委托之前在必要时调用SetSynchronizationContext ,但是-如果我错了,请纠正我-名称“ Active Object”似乎暗示着一个类似于单线程STA的模型。 如果是这种情况,则Post无需进行设置; 它只需要在正确的线程上执行委托,该线程应该已经具有正确的当前同步上下文。

如果有帮助,我有一个单线程SynchronizationContext 在这里 ,但它不会做任何形式的(明确的)消息抽水。

正如@StephenCleary在评论中提到的那样,问题在于,在我的真实代码中,我在async方法中但在await关键字之前设置了Sync Context,我认为这是可行的,但显然没有用(我猜测状态机器在方法开始时获取上下文?)。

解决方案是首先以非异步方法设置同步上下文。

错误

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

        // do work

        await Task.Delay(500);

        // Sync Context now cleared
    });

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