【发布时间】: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