AsyncLocal 值在 ThreadContextChanged 上更新为 null

本文关键字:null 更新 值在 ThreadContextChanged AsyncLocal | 更新日期: 2023-09-27 18:37:28

我试图了解AsyncLocal应该如何在.Net 4.6中工作。我正在将一些数据放入AsyncLocal...但是当线程上下文更改时,它被设置为空。我使用 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;
    }
}

下面是如何调用它的示例。这是使用事件存储连接 (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()));
    }

这是我的黑客解决方案,它似乎有效:

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

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

AsyncLocal 值在 ThreadContextChanged 上更新为 null

你需要

做的是: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