跨线程的HTTPContext
本文关键字:HTTPContext 线程 | 更新日期: 2023-09-27 18:29:40
我需要为每个web请求实例化一个singleton对象,这样数据就可以处理一次,并且在整个请求过程中都是有效的,我在HTTP请求期间使用HttpContext.Current.Items
共享数据,一切都很好,直到我们需要跨多个线程的singleton对象实例,我想到的第一件事是将HttpContext实例传递给新线程:
HttpContext context = HttpContext.Current;
ThreadPool.QueueUserWorkItem(callback =>
{
HttpContext.Current = context;
// blah blah
});
我不认为这是一个线程安全的方法,正如这里所指出的。
使用Reflector,我发现HttpContext.Current.Items实际上使用CallContext在每个逻辑线程中存储对象。所以我将singleton接口改为:
public static SingletonType SingletonInstance
{
get { return CallContext.GetData(key) as SingletonType; }
set { CallContext.SetData(key, value); }
}
在启动任何新线程时,只需覆盖SingletonInstance
!代码运行良好,但似乎在重载情况下,CallContext.GetData(键)返回null,应用程序崩溃,出现null引用异常!
我在想,如果CallContext.GetData
是原子的?但这似乎并不正确,CallContext是线程特定的数据存储,必须是原子的,否则我就没有抓住要点!
我的另一个猜测是,设置SingletonInstance(CallContext.SetData)发生在一个线程中,而CallContext.GetData在另一个线程执行,如这里所述,但我不知道如何/为什么?
更新:
我们在服务器上的一个数组中保留每个在线用户的一个实例。singleton对象实际上是对表示当前用户的对象的引用。当前用户必须是唯一的,并且在每个线程中都可以用于数据库查询、日志记录、错误处理等,这就是它的实现方式:
public static ApplicationUser CurrentUser
{
get { return CallContext.GetData("ApplicationUser") as ApplicationUser ; }
set { CallContext.SetData("ApplicationUser", value); }
}
ASP.NET可能会在负载不足的情况下在线程之间迁移请求。一旦接收到请求,页面构造函数就可以在一个线程上执行,并在另一个线程中加载页面。在这个线程切换中,CallContext和ThreadStatic没有被迁移,但luckaly HttpContext是.
这可能会产生误导,因为HttpContext是调用上下文,但这在ASP.NET中有点奇怪,可能是因为偷工减料以提高性能。
您必须删除对CallContext的依赖关系,并全程使用HttpContext。
你可以在Piers7的这篇精彩的博客文章中阅读更多细节。
这是在聊天会话中解决的。
从本质上讲,它涉及长时间运行的任务,使用外部服务(Web或常规Windows服务)的建议被认为是解决该问题的最佳方案。
线程保护第二种方法是最好的方法。这是你的singletone的线程安全版本:
public sealed class SingletonType
{
#region thread-safe singletone
private static object _lock = new object();
private SingletonType() { }
public static SingletonType SingletonInstance
{
get
{
if (CallContext.GetData(key) == null)
{
lock (_lock)
{
if (CallContext.GetData(key) == null)
CallContext.SetData(key, new SingletonType());
}
}
return CallContext.GetData(key) as SingletonType;
}
}
#endregion
//
//
// SingletoneType members
//
//
}
注意:使用lock { }
块是关键。