aov*_*ven 8 c# asp.net async-await
我有一个针对.NET 4.6的ASP.NET应用程序,我疯狂地试图找出为什么HttpContext.Current在我的异步MVC控制器操作中第一次等待之后变为null.
我检查并三重检查我的项目是针对v4.6,web.config的targetFramework属性也是4.6.
SynchronizationContext.Current在await之前和之后分配,它是正确的,即AspNetSynchronizationContext不是遗留的.
FWIW,await有问题确实在继续时切换线程,这可能是由于它调用外部I/O绑定代码(异步数据库调用)但这应该不是问题,AFAIU.
然而,它是!HttpContext.Current变为null 的事实导致我的代码出现了许多问题,对我来说没有任何意义.
我检查了通常的建议,我很肯定我正在做我应该做的一切.我的代码中也完全没有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);
}
Run Code Online (Sandbox Code Playgroud)
我需要这两个因为自定义授权和清理逻辑,这需要异步.AFAIU,这是支持的,不应该是一个问题.
还有什么可能是我看到这种令人费解的行为的原因?
更新:补充观察.
SynchronizationContext在等待与等待之前,引用保持不变.但它的内部变化在下面的截图中可以看到!
我不确定这与我的问题有什么关系(或者甚至是否).希望别人能看到它!
我决定定义一个手表HttpContext.Current,并开始“进入”等待状态,看看它到底在哪里发生了变化。毫不奇怪,随着我的继续,线程被切换了多次,这对我来说很有意义,因为途中有多个真正的异步调用。他们都HttpContext.Current按照应有的方式保存了实例。
然后我就犯规了……
var observer = new EventObserver();
using (EventMonitor.Instance.Observe(observer, ...))
{
await plan.ExecuteAsync(...);
}
var events = await observer.Task; // Doh!
Run Code Online (Sandbox Code Playgroud)
简短的解释是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(...);
}
}
Run Code Online (Sandbox Code Playgroud)
SetSynchronizationContext我现在可以看到,之前的调用SetResult并没有达到我希望的效果。目标是将原始同步上下文应用于该行的延续await observer.Task。
现在的问题是:我该如何正确地做到这一点?我猜它会ContinueWith在某个地方进行显式调用。
更新
这就是我所做的。我传递了TaskCreationOptions.RunContinuationsAsynchronouslyTaskCompletionSource 构造函数选项,并修改了类上的 Task 属性EventObserver以包含显式同步的延续:
public Task<T> Task
{
get
{
return _source.Task.ContinueWith(t =>
{
if (_capturedContext != null)
{
SynchronizationContext.SetSynchronizationContext(_capturedContext);
}
return t.Result;
});
}
}
Run Code Online (Sandbox Code Playgroud)
所以现在,当代码调用 时await observer.Task,延续将确保首先输入正确的上下文。到目前为止,它似乎工作正常!
| 归档时间: |
|
| 查看次数: |
1341 次 |
| 最近记录: |