我有一个针对 .NET 4.6 的 ASP.NET 应用程序,我疯狂地试图弄清楚为什么 HttpContext.Current
在我的异步 MVC Controller 操作中第一次等待后变为 null。
我已经检查并三次检查我的项目以 v4.6 为目标,并且 web.config 的 targetFramework
属性也是 4.6。
SynchronizationContext.Current
在 await 之前和之后都被分配,它是正确的,即 AspNetSynchronizationContext
,而不是旧的。
FWIW,有问题的 await 确实会在继续时切换线程,这可能是因为它调用了外部 I/O 绑定(bind)代码(异步数据库调用),但这应该不是问题,AFAIU。
然而,它是! HttpContext.Current
变为 null 的事实给我的代码带来了很多问题,这对我来说没有任何意义。
我检查过 the usual recommendations我很肯定我正在做我应该做的一切。我的代码中也绝对没有 ConfigureAwait
!
我所拥有的是我的 HttpApplication
实例上的几个异步事件处理程序:
public MvcApplication()
{
var helper = new EventHandlerTaskAsyncHelper(Application_PreRequestHandlerExecuteAsync);
AddOnPreRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler);
helper = new EventHandlerTaskAsyncHelper(Application_PostRequestHandlerExecuteAsync);
AddOnPostRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler);
}
我需要这两个,因为自定义授权和清理逻辑需要异步。 AFAIU,这是受支持的,应该不是问题。
还有什么可能是我所看到的这种令人费解的行为的原因?
更新:额外观察。
SynchronizationContext
引用在 await 之后和 await 之前保持不变。但是它的内部结构在两者之间发生了变化,如下面的屏幕截图所示!
目前我不确定这与我的问题有何关联(甚至是否关联)。希望其他人能看到它!
最佳答案
我决定在 HttpContext.Current
上定义一个 watch,并开始“进入”await 以查看它究竟在哪里发生变化。毫不奇怪,随着我的继续,线程被切换了多次,这对我来说很有意义,因为途中有多个真正的异步调用。他们都按照预期保留了 HttpContext.Current
实例。
然后我就犯了错误...
var observer = new EventObserver();
using (EventMonitor.Instance.Observe(observer, ...))
{
await plan.ExecuteAsync(...);
}
var events = await observer.Task; // Doh!
简短的解释是 plan.ExecuteAsync
执行许多步骤,这些步骤通过专用线程以非阻塞方式报告给专门的事件日志。这是商业软件,报告事件的模式在整个代码中被广泛使用。大多数时候,调用者并不直接关心这些事件。但是有一两个地方比较特殊,因为调用者想知道执行某个代码后发生了哪些事件。那就是使用 EventObserver
实例的时候,如上所示。
为了等待所有相关事件被处理和观察,await observer.Task
是必需的。有问题的任务来自观察者拥有的 TaskCompletionSource
实例。一旦所有事件都已流入,源的 SetResult
就会从处理事件的线程中调用。我最初对这个细节的实现是——非常天真——如下:
public class EventObserver : IObserver<T>
{
private readonly ObservedEvents _events = new ObservedEvents();
private readonly TaskCompletionSource<T> _source;
private readonly SynchronizationContext _capturedContext;
public EventObserver()
{
_source = new TaskCompletionSource<T>();
// Capture the current synchronization context.
_capturedContext = SynchronizationContext.Current;
}
void OnCompleted()
{
// Apply the captured synchronization context.
SynchronizationContext.SetSynchronizationContext(_capturedContext);
_source.SetResult(...);
}
}
我现在可以看到,在 SetResult
之前调用 SetSynchronizationContext
并没有达到我希望的效果。目标是将原始同步上下文应用于 await observer.Task
行的延续。
现在的问题是:我该如何正确地做到这一点?我猜它会在某处显式调用 ContinueWith
。
更新
这就是我所做的。我将 TaskCreationOptions.RunContinuationsAsynchronously
选项传递给了 TaskCompletionSource 构造函数,并修改了我的 EventObserver
类上的 Task 属性以包含显式同步延续:
public Task<T> Task
{
get
{
return _source.Task.ContinueWith(t =>
{
if (_capturedContext != null)
{
SynchronizationContext.SetSynchronizationContext(_capturedContext);
}
return t.Result;
});
}
}
现在,当代码调用 await observer.Task
时,延续将确保首先输入正确的上下文。到目前为止,它似乎工作正常!
关于c# - ASP.NET 4.6 异步 Controller 方法在等待后丢失 HttpContext.Current,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43585701/