HttpContext.Current 在异步回调中为空

Posted

技术标签:

【中文标题】HttpContext.Current 在异步回调中为空【英文标题】:HttpContext.Current is null in an asynchronous Callback 【发布时间】:2014-08-24 00:29:55 【问题描述】:

尝试在方法回调中访问HttpContext.Current,以便我可以修改Session 变量,但是我收到HttpContext.Currentnull 的异常。回调方法在_anAgent 对象触发时异步触发。

在 SO 上查看 similar questions 后,我仍然不确定解决方案。

我的代码的简化版本如下所示:

public partial class Index : System.Web.UI.Page

  protected void Page_Load()
  
    // aCallback is an Action<string>, triggered when a callback is received
    _anAgent = new WorkAgent(...,
                             aCallback: Callback);
    ...
    HttpContext.Current.Session["str_var"] = _someStrVariable;
  

  protected void SendData() // Called on button click
  
    ...
    var some_str_variable = HttpContext.Current.Session["str_var"];

    // The agent sends a message to another server and waits for a call back
    // which triggers a method, asynchronously.
    _anAgent.DispatchMessage(some_str_variable, some_string_event)
  

  // This method is triggered by the _webAgent
  protected void Callback(string aStr)
  
    // ** This culprit throws the null exception **
    HttpContext.Current.Session["str_var"] = aStr;
  

  [WebMethod(EnableSession = true)]
  public static string GetSessionVar()
  
    return HttpContext.Current.Session["str_var"]
  

不确定是否有必要,但我的 WorkAgent 课程如下所示:

public class WorkAgent

  public Action<string> OnCallbackReceived  get; private set; 

  public WorkAgent(...,
                   Action<string> aCallback = null)
  
    ...
    OnCallbackReceived = aCallback;
  

  ...

  // This method is triggered when a response is received from another server
  public BackendReceived(...)
  
    ...
    OnCallbackReceived(some_string);
  

代码中发生了什么: 单击按钮调用SendData() 方法,其中_webAgent 将消息发送到另一个服务器并等待回复(同时用户仍然可以与此页面交互并引用相同的SessionID)。一旦收到它就会调用BackendReceived() 方法,该方法在.aspx.cs 页面中调用Callback() 方法。

问题:WorkAgent 触发Callback() 方法时,它会尝试访问HttpContext.Current,即null。为什么会出现这种情况,如果我继续忽略异常,我仍然可以使用 ajax 返回的GetSessionVar() 方法获得相同的SessionIDSession 变量。

我应该启用aspNetCompatibilityEnabled 设置吗?我应该创建某种asynchronous module handler 吗?这与Integrated/Classic mode 有关吗?

【问题讨论】:

为什么要使用回调来复杂化这个问题,更好的解决方案可能是从客户端使用 ajax,这样用户仍然可以与网站交互。而对其他系统的调用可以只是普通的方法调用 Ajax 大部分是从客户端使用的,只是没有将它包含在上面的代码中(它更新了HttpContext Session 变量和SQL 数据库)。唯一不是 ajax 调用的方法是SendData()。这会将数据发送到其他一些服务器。我只是很困惑为什么 HttpContect.Current 在回调中变为 null 请查看我的回答,了解为什么会发生这种情况 【参考方案1】:

这是一个基于类的解决方案,目前在 MVC5 中适用于简单的情况(MVC6 支持基于 DI 的上下文)。

using System.Threading;
using System.Web;

namespace SomeNamespace.Server.ServerCommon.Utility

    /// <summary>
    /// Preserve HttpContext.Current across async/await calls.  
    /// Usage: Set it at beginning of request and clear at end of request.
    /// </summary>
    static public class HttpContextProvider
    
        /// <summary>
        /// Property to help ensure a non-null HttpContext.Current.
        /// Accessing the property will also set the original HttpContext.Current if it was null.
        /// </summary>
        static public HttpContext Current => HttpContext.Current ?? (HttpContext.Current = __httpContextAsyncLocal?.Value);

        /// <summary>
        /// MVC5 does not preserve HttpContext across async/await calls.  This can be used as a fallback when it is null.
        /// It is initialzed/cleared within BeginRequest()/EndRequest()
        /// MVC6 may have resolved this issue since constructor DI can pass in an HttpContextAccessor.
        /// </summary>
        static private AsyncLocal<HttpContext> __httpContextAsyncLocal = new AsyncLocal<HttpContext>();

        /// <summary>
        /// Make the current HttpContext.Current available across async/await boundaries.
        /// </summary>
        static public void OnBeginRequest()
        
            __httpContextAsyncLocal.Value = HttpContext.Current;
        

        /// <summary>
        /// Stops referencing the current httpcontext
        /// </summary>
        static public void OnEndRequest()
        
            __httpContextAsyncLocal.Value = null;
        
    

要使用它可以从 Global.asax.cs 挂钩:

    public MvcApplication() // constructor
                
        PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);
        EndRequest += new EventHandler(OnEndRequest);
     

    protected void OnPreRequestHandlerExecute(object sender, EventArgs e)
    
        HttpContextProvider.OnBeginRequest();   // preserves HttpContext.Current for use across async/await boundaries.            
    

    protected void OnEndRequest(object sender, EventArgs e)
    
        HttpContextProvider.OnEndRequest();
    

然后可以用这个代替HttpContext.Current:

    HttpContextProvider.Current

可能有问题,因为我目前不明白这个related answer。请发表评论。

参考:AsyncLocal(需要 .NET 4.6)

【讨论】:

如果由于静态变量而同时发生两个请求,此解决方案是否会在请求之间混合上下文? @SimonTheCat,AsyncLocal 参考页面包含一个使用类似静态返回多个值的示例。 AsyncLocal Source code 表示它实际上是围绕 ExecutionContext 的某种类型的包装器,它是特定于线程的上下文。【参考方案2】:

请参阅以下文章,了解为什么 Session 变量为空,以及可能的解决方法

http://adventuresdotnet.blogspot.com/2010/10/httpcontextcurrent-and-threads-with.html

引用自文章;

当前的HttpContext实际上是在线程本地存储中,这就解释了为什么子线程无权访问它

正如作者所说的一项拟议工作

在您的子线程中传递对它的引用。在回调方法的“状态”对象中包含对HttpContext 的引用,然后您可以将其存储到该线程上的HttpContext.Current

【讨论】:

【参考方案3】:

当使用线程或async 函数时,HttpContext.Current 不可用。

尝试使用:

HttpContext current;
if(HttpContext != null && HttpContext.Current != null)

  current = HttpContext.Current;

else

    current = this.CurrentContext; 
    //**OR** current = threadInstance.CurrentContext; 

一旦您使用适当的实例设置current,您的其余代码都是独立的,无论是从线程调用还是直接从WebRequest 调用。

【讨论】:

以上是关于HttpContext.Current 在异步回调中为空的主要内容,如果未能解决你的问题,请参考以下文章

异步操作后的 HttpContext.Current.Items

WCF REST HttpContext.Current 异步/等待

将 HttpContext.Current.User 与异步等待一起使用的正确方法

异步任务,HttpContext.Current为null解决办法

异步 HttpContext.Current 为空null 另一种解决方法

HttpContext.Current并非无处不在