c# - ASP.NET 4.6 异步 Controller 方法在等待后丢失 HttpContext.Current

标签 c# asp.net async-await

我有一个针对 .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 之前保持不变。但是它的内部结构在两者之间发生了变化,如下面的屏幕截图所示!

等待之前: Before entering await

等待之后: After continuing

目前我不确定这与我的问题有何关联(甚至是否关联)。希望其他人能看到它!

最佳答案

我决定在 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/

相关文章:

javascript - 如何在没有评估的情况下检测异步功能支持?

Javascript 函数乱序完成——异步

c# - 如何对给定点之间的直线的所有像素位置进行采样?

c# - 如何在 web.config 中设置 session 超时

c# - 如何将自定义日志存储在使用diagnostics.wadcfg创建的Azure本地存储中

asp.net - Ajax AutoCompleteExtender——样式化单个项目?

javascript - 如何解决 CKEditor 在 IE 11 中的 javascript 错误

javascript - 异步和 promise 从快捷方式更改

c# - 在哪里放置代码以将用户重定向回页面直到谓词为真?

c# - 将十六进制颜色转换为整数