【问题标题】:AsyncLocal Value updated to null on ThreadContextChangedThreadContextChanged 上的 AsyncLocal 值更新为 null
【发布时间】:2016-06-18 12:57:59
【问题描述】:

我试图了解 AsyncLocal 在 .Net 4.6 中应该如何工作。我将一些数据放入 AsyncLocal ...但是当 ThreadContext 更改时,它被设置为 null。我使用 AsyncLocal 的全部原因是在等待异步操作时尝试跨线程保留/缓存此值。知道为什么会随着上下文的变化而专门调用它并将其设置为 null 吗? AsyncLocal 上的文档非常少……也许我都搞错了。

public class RequestContextProvider : IRequestContextProvider
{
    private static readonly AsyncLocal<IRequestContext> _requestContext = new AsyncLocal<IRequestContext>(ValueChangedHandler);

    private static void ValueChangedHandler(AsyncLocalValueChangedArgs<IRequestContext> asyncLocalValueChangedArgs)
    {
        **//This is just here to observe changes...when I await a call that 
        //causes control to pass to a different thread..this is called 
        //with a current value of null.**
        var previousValue = asyncLocalValueChangedArgs.PreviousValue;
        var currentValue = asyncLocalValueChangedArgs.CurrentValue;
        var contextChanged = asyncLocalValueChangedArgs.ThreadContextChanged;
    }

    public void SetContext(IRequestContext requestContext)
    {
        _requestContext.Value = requestContext;
    }

    public IRequestContext GetContext()
    {
        return _requestContext.Value;
    }

}

这是如何调用它的示例。这是使用 EventStore 连接 (GetEventStore.com) 调用的异步事件订阅者。如果这里的两个等待的任务不做任何事情(如果没有要查找的 ID)我没有问题,因为大概任务是同步运行的。但是,如果我确实有工作要做这些任务,我就会失去我的上下文。

 private async Task PublishProduct(Guid productId, Guid productReferenceId, IEnumerable<Guid> disclosureIds,
        IEnumerable<Guid> addOnIds)
    {
        var disclosureReferenceIdsTask = _disclosureReferenceIdService.GetReferenceIdsAsync(disclosureIds);
        var addOnReferenceIdsTask = _addOnReferenceIdService.GetReferenceIdsAsync(addOnIds);
        await Task.WhenAll(disclosureReferenceIdsTask, addOnReferenceIdsTask);

        IEnumerable<Guid> disclosuresResult = await disclosureReferenceIdsTask;
        IEnumerable<Guid> addOnsResult = await addOnReferenceIdsTask;

        await _eventPublisher.PublishAsync(new ProductPublished(productId, productReferenceId,
            disclosuresResult.ToList(), addOnsResult.ToList()));
    }

这是我的 hacky 解决方案,似乎可行:

    private static void ValueChangedHandler(AsyncLocalValueChangedArgs<IRequestContext> asyncLocalValueChangedArgs)
    {
        var previousValue = asyncLocalValueChangedArgs.PreviousValue;
        var currentValue = asyncLocalValueChangedArgs.CurrentValue;
        var contextChanged = asyncLocalValueChangedArgs.ThreadContextChanged;
        if (contextChanged && currentValue == null && previousValue != null)
        {
            _requestContext.Value = previousValue;
        }
    }

仅供参考,这是一个在 DNX RC1 下作为控制台应用运行的 4.6 运行时项目。

【问题讨论】:

  • 不应该将其存储在 ExecutionContext 中而不是 SynchronizationContext 中吗?
  • @StephenCleary - 当然我会试一试,看看会发生什么。我认为我已经将 IRequestContext 提供程序限定为 DI 中的单例,所以很确定我可以删除“静态”
  • @sannee:一步一步降低复杂性。如果问题突然消失,那么您就有很高的信心是哪一步导致了问题。
  • @StephenCleary - 好吧,AysncLocal 在中间件和控制器上运行良好。看起来我正在处理的实际上是 Autofac 在使用 PerLifetimeScope 时的一些奇怪的范围界定行为。在某些情况下,它会以某种方式改变上下文,使其无法访问当前的 AsyncLocal 值......不确定它在后台做了什么,但我相信我已经通过使用不同的范围方法解决了它。
  • @sannee 有没有弄清楚确切的原因?在使用ServiceProviderFactory 在后台任务中创建范围时,我在使用默认的 asp.net 核心 ServiceProvider 时遇到了同样的问题。可悲的是真的很难在较小的样本中重现..

标签: c# async-await dnx


【解决方案1】:

你需要做的是:ExecutionContext.SuppressFlow();
当您的线程上下文丢失时,这将停止引发事件valueChangedHandler,因此您将无法获得NULL 值,并且当创建新的ThreadContext 并将数据复制到此时,它将停止引发事件。

private static void ValueChanged(AsyncLocalValueChangedArgs<string> obj)
{
    Console.WriteLine(obj.CurrentValue);
}

public static void Main(string[] args)
{
    ExecutionContext.SuppressFlow();
    AsyncLocalContext.Value = "Main";
    Task.Run(() =>
        {
            AsyncLocalContext.Value = "Test1";
        }).Wait();

    Console.WriteLine("Main: " + AsyncLocalContext.Value);
}

输出是:

Main
Test1
Main: Main

如果我们评论 ExecutionContext.SuppressFlow(); 那么会得到这个:

Main
Main        -- copied data to Task
Test1
            -- here is NULL when context has lost
Main: Main

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-10
    • 2017-01-04
    • 2019-05-29
    • 1970-01-01
    • 1970-01-01
    • 2013-08-12
    • 1970-01-01
    相关资源
    最近更新 更多