跨线程使用HTTPContext

13

用户访问spawn.aspx页面,该页面然后生成半打线程,呈现所有使用的页面

 ((System.Web.IHttpHandler)instance).ProcessRequest(reference to spawn's HTTPContext);

不用担心ASP.Net似乎向用户发送了7个响应,这部分已经被处理,只有一个响应会被发送。

问题在于,在高流量环境(我们的生产环境)中,由于存在多个线程(四核四线程),我们会遇到错误:

System.IndexOutOfRangeException 
at System.collections.ArrayList.Add 
at System.Web.ResponseDependencyList.AddDependencies(String[] items, String argname, Boolean cloneArray, DateTime utcDepTime) 
at System.Web.ResponseDependencyList.AddDependencies(String[] items, String argname, Boolean cloneArray, String requestVritualPath)
at System.Web.UI.Page.AddWrappedFileDependencies(Object virtualFileDependencies) 
at ASP.spawned_page_no_1_aspx.FrameworkInitialize()
at System.Web.UI.Page.ProcessRequest
我们无法在其他地方复制它。我的同事认为这是因为我正在重用原始的HTTPContext并将其传递到其他线程中,而且它不是线程安全的。
按照这个逻辑,我尝试创建一个新的HTTPContext传递给线程。但其中的某些部分似乎无法“组合”。具体而言,我需要将Session对象放入新的HTTPContext中。我想我也想要将其他部分放入其中,比如Cache。 HTTPContext.Current.Session.IsSynchronized记录为false。
我的问题是:
1.您是否认为错误来自跨线程使用HTTPContext? 2.我该如何解决它? 3.如果解决方法是为每个线程复制HTTPContext,那么如何将Session(和Cache)放入新的上下文中?Request和Response在构造函数中被设置,但Session不可设置。
编辑:更多细节
所以回到这个陈述:“不要担心ASP.Net似乎会为1个请求发送7个响应,这部分已经处理好了,只会发送一个响应。”我非常欣赏Raymond Chen,我同意你:“现在你有两个问题”是在没有更多信息的情况下的合理陈述。
实际上发生的事情是,我正在构建一个Excel文档进行发送。在spawn.aspx页面中,它设置了一些状态信息,包括正在渲染到Excel,以及执行渲染的对象。每个生成的页面都会获得该信息,并且会阻塞,直到轮到它们进行渲染对象。它看起来确实是这样:
 protected override void Render(System.Web.UI.HtmlTextWriter writer)
 {
    if (this.RenderToExcel)
    {
      Deadlocker.SpinUntilCurrent(DeadLockToken);
      RenderReport(this, this.XLSWriter);
      Deadlocker.Remove(DeadLockToken);
    }
    else
      base.Render(writer);
 }
但是在这一点之前的所有处理 - 数据库访问,控制层次等都是并行完成的。而且这些处理非常多 - 足够让并行化处理仍然允许其在渲染时阻塞,从而将总时间缩短超过一半。
最好的部分是 - 为Excel渲染不需要重写任何内容。所有控件都知道如何将自己呈现给Excel,并且您可以单独访问每个产生的页面(实际上这是“正常情况”- Excel报告只是所有生成页面的聚合)。
因此我认为最终结果会是“你无法做到这一点,你需要重新考虑方法”,但我必须至少尝试一下,因为一切都如此完美,没有重复任何逻辑或代码,也没有必要抽象任何内容。问题只在于多线程处理,如果我以串行方式呈现页面,则一切都很好,只是速度较慢。
4个回答

4

虽然HttpContext被设计用于处理不特定于线程的上下文(因为HttpContext可以在一个线程上开始,在另一个线程上结束),但它并不是隐式线程安全的。

本质上,问题在于您正在执行不打算进行的操作,这些请求通常会是多个,并且每个请求都有自己分配的HttpApplication来满足请求,并且每个请求都有自己的HttpContext。

我真的建议尝试让asp.net基础架构自己委托请求。


2

你的同事们是正确的,如果一个线程锁定了一个资源,而另一个线程试图使用它,那么你的线程池就会崩溃!这并不是一个很好的结果。大多数人通过创建新对象并将它们传递到参数化线程来解决这个问题。如果你绝对需要使用相同的对象,那么你必须实现一些代码,首先检查资源是否正在被另一个线程使用,然后等待一段时间再次检查。一个例子是创建一个IsInUse布尔值,你总是首先检查它,然后你的线程在使用该资源时将其设置为true,完成后将其设置为false,防止其他线程尝试使用底层资源(你的httpContext)。希望这能帮到你。


所有这些锁定可能很难安排,他得到的线程异常来自Page类,该类正在改变http上下文,除非他可以覆盖执行此操作的页面中的动作,并放置一个锁定,否则锁定解决方案将无法工作。 - meandmycode
1
很好的评论,我同意你的观点。我的偏好是传递一个全新的对象,该对象从线程创建时的HTTP上下文中派生一些信息。这将是非常可靠的。 - Al Katawazi

1

由于HttpContext是.Net库的一部分,我期望所有静态函数都是线程安全的,但任何非静态成员在调用相同对象实例时都不是线程安全的。所以希望下次您能预见到在跨线程共享HttpContext实例会有问题。

您是否有办法将需要并行执行的操作与HttpContext分离?如果它们只是加载数据并写入CSV格式,那么该代码对ASP.NET用户控件或页面生命周期没有必要的依赖性。一旦删除了该依赖性,您可以使用异步HttpHandler实现页面,在IHttpHandler.BeginProcessingRequest()和IHttpHandler.EndProcessingRequest()之间运行并行操作。


1
我会确保每次访问HttpContext.Current.Items集合时,都在HttpContext.Current.Items.SyncRoot对象上使用监视器锁(C#)/SyncLock(VB)块来包装您的调用。

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