从不同线程访问HttpContext.Current

57

我有一个C# ASP.NET应用程序,启动了大约25个不同的线程来运行SiteCrawler.cs类中的一些方法。

HttpContext.Current.Session中,我想保存用户进行的搜索结果,并在所有线程完成运行后向用户呈现它。我的问题是,在派生的线程中,HttpContext.Current对象为空,因为该对象在那里不存在。

除了使用会受到多线程应用程序限制的session之外,我还有什么其他选项可以保存用户/会话特定数据?

我已经尝试过四处搜索Stackoverflow以找到解决方案,但没有任何运气...


1
我猜你可以在创建新线程时传递当前的HttpContext,并在其中更新会话。 - musefan
4
在面向Web的应用程序中,为“长时间运行的进程”生成线程通常是个不好的主意(一般不建议这么做)。 - Grant Thomas
1
musefan - 我考虑过这个,但我不确定这是否是一个好主意,因为生成的线程会更改传递的HttpContext的内容,而此时它也将是不同的。 - Raydk
我一直在寻找解决相关问题的方法,似乎“正确”流动HttpContext的方法是通过使用SynchronizationContext。我不会在这里详细说明,我只会指向这篇详尽而写得很好的博客文章,它回答了你关于HttpContext的问题,以及更复杂的线程场景。在这篇文章中,他提到了Stephen Toub的经典文章[ExecutionContext vs SynchronizationContext](http - Mark
@GrantThomas 这里有一个严肃的问题:Chrome对查询的超时时间(太短了)我们无法控制(实际上非常不一致)。那么,您建议如何构建长时间运行查询的调用? - David I. McIntosh
显示剩余2条评论
5个回答

84

在我的应用程序中,有很多使用HttpContext.Current的代码,我无法修改这些代码。

下面示例中的worker.DoWork()使用了该代码。而且我必须在单独的线程中运行它。

我想到了以下解决方案:

 HttpContext ctx = HttpContext.Current;
 Thread t = new Thread(new ThreadStart(() =>
                {
                    HttpContext.Current = ctx;
                    worker.DoWork();
                }));
 t.Start();
 // [... do other job ...]
 t.Join();

11
请注意,通常情况下这样做是不好的。例如,有时您可能会发现HttpContext.Current.Items[x]返回null而不是您放置在那里的值,因为ASP在HTTP请求完成后清理了资源。这是一种快速但不太规范的方法,大部分情况下可能有效,但不要依赖它处理任何重要的事情。 - Rory
3
我不同意@Rory的观点。与其他选项相比,我认为这是迄今为止最优雅的解决方案。首先,你没有克隆HttpContext,而是只传递了一个引用。由于线程可能运行时间比HttpRequest本身长,因此在访问它之前很容易检查HttpContext是否为空。作为上述代码段的改进,可以通过WeakReference传递“ctx”——这样,如果需要,线程就不会阻止HttpContext被垃圾回收。再次根据所需的生命周期调整上面的代码以适应您的需求。+ 已接受的答案 - AlexVPerl
1
@Rory 这意味着一个“fire-and-forget”异步调用,而那个是一个坏主意。如果您没有做什么奇怪的事情,并确保网络请求等待所有异步调用完成,那么应该没问题,对吧? - user247702
2
当我写下先前评论时,我想我没有注意到这个答案中的t.Join()调用。如果后台线程在http请求之前完成,我同意它应该是没问题的。但就我个人经验而言,当网络请求先完成并且后台任务仍在运行时,就会出现问题。需要注意的是问题并不是HttpContext被垃圾回收了 - 显然还有一个引用,因此它不会被垃圾回收 - 而是ASP.NET中的某些东西会删除HttpContext.Current中的所有元素,因此如果您希望集合中有任何内容,那就真的很倒霉了。 - Rory
帮助我在ASP.NET应用程序中使用并行库和会话。 - Dinesh Kumar
显示剩余4条评论

11

1
那篇关于“使用HttpContext.Current”的文章对我解决问题非常有帮助,谢谢。 - mungflesh

7

@Rory在上面发表了评论,指出即使将HttpContext对象传递到线程中,某些对象仍会变为空。这在我的User属性中发生过。因此,您可以像这样将User复制到线程的CurrentPrincipal中:

在控制器上下文中,保存用户:

            _user = HttpContext.Current.User;
            var processThread = new Thread(() => ThreadedCode());
            processThread.Start();

在线程中设置“线程”的用户:

    private static void ThreadedCode()
    {
        // Workaround for HttpContext.Current.User being null.
        // Needed for CreatedBy and RevisedBy.
        Thread.CurrentPrincipal = _user;

注意,HttpContext 仅在请求的生命周期内可用。线程可能会比请求存在更长时间,这可能是你需要线程的原因! :)

1
@Rory,感谢您上面的评论。这让我开始思考另一种答案。 - Jess

5
只需在 SiteCrawler.cs 类的构造函数中添加 HttpContext.Current 即可。
public class SiteCrawler
{
     HttpContext context = HttpContext.Current;

    public void Method()
    {
        context.WhateverYouWant
    }
}

3
您可以将其保存到数据库中,然后让用户的浏览器不断刷新或使用ajax或使用新的SignalR来检查结果是否已经写入到数据库中。希望这可以帮助您。

将会话变量保存到数据库中并不是问题。我只是不知道将其绑定到正确的会话并在线程完成时检索它的最佳方法是什么。 - Raydk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接