我需要为每个Web请求实例化一个单例对象,以便数据处理一次并在整个请求中有效,我在HTTP请求期间使用HttpContext.Current.Items
来共享数据,一切都很好,直到我们需要跨多个线程的单例对象实例,我想到的第一件事是将HttpContext实例传递给新线程:
HttpContext context = HttpContext.Current;
ThreadPool.QueueUserWorkItem(callback =>
{
HttpContext.Current = context;
// blah blah
});
我不认为这是一种线程安全的方法noted here。
使用Reflector我认为HttpContext.Current.Items实际上使用 CallContext 来存储每个逻辑线程中的对象。所以我将单例界面更改为:
public static SingletonType SingletonInstance
{
get { return CallContext.GetData(key) as SingletonType; }
set { CallContext.SetData(key, value); }
}
在启动任何新线程时只需覆盖SingletonInstance
!代码工作正常,但似乎在某种程度上负载很重,CallContext.GetData(key)返回null,应用程序崩溃与空引用异常!
我在想,如果CallContext.GetData
是原子的?但它似乎不对,CallContext是特定于线程的数据存储,必须是原子的,否则我就错过了这一点!
我的另一个猜测是设置SingletonInstance(CallContext.SetData)发生在一个线程中,而CallContext.GetData在另一个线程中执行noted here但我不知道如何/为什么?
更新
我们将每个在线用户的实例保存在服务器上的数组中。单例对象实际上是对表示当前用户的对象的引用。当前用户必须是唯一的,并且在每个线程中都可用于数据库查询,日志记录,错误处理等等,这就是它的完成方式:
public static ApplicationUser CurrentUser
{
get { return CallContext.GetData("ApplicationUser") as ApplicationUser ; }
set { CallContext.SetData("ApplicationUser", value); }
}
答案 0 :(得分:3)
如果线程处于负载状态,ASP.NET可能会在线程之间迁移请求。收到请求后,页面构造函数可以在一个线程上执行,而页面加载在另一个线程上。在这个线程中,不会迁移CallContext和ThreadStatic,但幸运的是HttpContext。
这可能会产生误导,因为HttpContext是调用上下文,但这在ASP.NET中有点怪癖,可能是因为为了提高性能而偷工减料。
你必须删除对CallContext的依赖,并通过整个方式使用HttpContext。
您可以通过Piers7在此terrific blog post中阅读更多详细信息。
答案 1 :(得分:1)
在聊天会话期间解决了这个问题。
本质上它涉及长时间运行的任务,并且建议使用外部服务(Web或常规Windows服务)被认为是解决问题的最佳方案。
答案 2 :(得分:0)
线程安全你的第二种方法是最好的方法。 这是单线程的线程安全版本:
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 { }
块是关键。