我这里有个问题。在我的网络应用程序中,我有一个页面启动另一个线程来执行耗时的任务。在这个新线程中,我调用了我的一种架构方法(在另一个项目中 - 一个架构项目)。问题是:在其中一种方法中,我访问了HttpContext.Current.Session字段。但是当我启动应用程序时,会抛出一个异常,说这个对象(HttpContext.Current.Session)有一个空引用。我如何将新线程的上下文设置为与 HttpApplication 上下文相同以访问HttpContext.Current.Session?
3 回答
这里有很多事情需要考虑。
如果您的线程的生命周期与页面的生命周期相同,并且您需要大量随机访问HttpSessionState
,那么您应该从使用静态属性SynchronizationContext
创建后台线程的调用中获取。Current
一旦你有了它,你可以将它传递给你的线程,然后当你需要访问与请求相关联的任何东西HttpContextBase
(这包括会话)时,你可以调用你传递给线程的Post
方法来获取值SynchronizationContext
(或设置它们):
// From thread servicing request.
var sc = SynchronizationContext.Current;
// Run the task
Task t = Task.Run(() => {
// Do other stuff.
// ...
// The value to get from the session.
string sessionValue = null;
// Need to get something from the session?
sc.Post(() => {
// Get the value.
sessionValue = HttpContext.Current.Session["sessionValue"];
}
// Do other stuff.
// ...
});
这样做很重要,因为访问HttpContextBase
(以及与之相关的任何内容)不是线程安全的,并且与处理请求的线程(嗯,上下文)相关联。
请注意,该Post
方法不会阻塞,因此调用之后的代码Post
(即 之后的行// Do other stuff.
)应该独立于传递给的委托Post
。如果后面的代码是依赖的,需要等待调用完成再继续,那么可以调用Send
方法;它具有相同的签名,并且会一直阻塞,直到委托中的代码被执行。
也就是说,如果您只想对值进行只读访问,那么最好在调用代码之前获取它们,然后在后台线程中访问它们:
// Get the values needed in the background thread here.
var values = {
SessionValue = HttpContext.Current.Session["sessionValue"];
};
// Run the task
Task t = Task.Run(() => {
// Do other stuff.
// ...
// Work with the session value.
if (values.SessionValue == ...)
// Do other stuff.
// ...
});
如果你的线程在请求被处理后还要继续处理,那么你只有只读状态,你必须在启动线程之前捕获它。一旦请求得到服务,即使会话存在,它也是一个逻辑概念;根据会话状态的提供者(会话状态管理器、SQL Server 等),每次有新请求进来时,对象都可能会被水合。
您还必须处理会话超时问题,您甚至不知道会话是否存在于您想要访问它的点。
如果将当前上下文传递给子线程,问题在于它依赖于父上下文。如果父上下文死了,那么你的线程将无法再访问上下文并且它会导致问题。
一种解决方案是克隆父上下文,然后在线程中使用克隆。这样,如果父线程处置,该线程将继续工作并可以访问所有上下文好东西。
HttpContext ctx = ThreadingFixHttpContext();
Thread newThread = new System.Threading.Thread(new ThreadStart(() =>
{
HttpContext.Current = ctx;
Thread.CurrentPrincipal = ctx.User;
var test = HttpContext.Current.Session["testKey"];
}));
newThread.Start();
这也应该适用于 Task.Run() 方式。完成这项工作的方法:
private static Object CloneObject(Object Source)
{
MemoryStream Stream = new MemoryStream();
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter Formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
Formatter.Serialize(Stream, Source);
Stream.Position = 0;
object Clone = (object)Formatter.Deserialize(Stream);
Stream.Close(); Stream.Dispose();
return Clone;
}
public static System.Web.HttpContext ThreadingFixHttpContext()
{
//If this method is called from a new thread there is issues holding httpContext.current (which is injected from parent thread in AniReturnedPaymentsFetch.ascx.cs
//The parent http current will die of its own accord (because it is from a different thread)
//So we clone it into thread current principal.
System.Security.Principal.WindowsIdentity ThreadIdentity =
(System.Security.Principal.WindowsIdentity)CloneObject(System.Web.HttpContext.Current.User.Identity);
//Then create a new httpcontext using the parent's request & response, so now the http current belongs to this thread and will not die.
var request = System.Web.HttpContext.Current.Request;
var response = System.Web.HttpContext.Current.Response;
var ctx = new System.Web.HttpContext(request, response);
ctx.User = new System.Security.Principal.WindowsPrincipal(ThreadIdentity);
return ctx;
}
您无法从线程中访问会话,但您可以使用以下方式共享数据:HttpRuntime.Cache
不过有几件事要记住:与会话不同,缓存确实会过期。此外,缓存在所有 Web 用户之间共享。