【问题标题】:HTTPContext across threads跨线程的 HTTPContext
【发布时间】:2012-04-14 23:53:17
【问题描述】:

我需要为每个 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 并且应用程序因 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); }
}

【问题讨论】:

  • 一个 HttpContext 指的是单个 Http 请求。在线程中的某个地方排队会导致一些问题(正如您显然已经发现的那样)。也许如果您描述一下您正在尝试做什么,我们可以想出一个更好的解决方案?
  • 问题很简单,我需要在我的应用程序中使用任何请求(可能是应用程序开始请求)实例化的单个对象引用,并且在整个请求中保持活动状态,任何可能之后开始的线程

标签: c# asp.net multithreading thread-safety singleton


【解决方案1】:

如果负载不足,ASP.NET 可能会在线程之间迁移请求。一旦收到请求,页面构造函数就可以在一个线程上执行,而页面加载在另一个线程上。在这个线程中,CallContext 和 ThreadStatic 没有被迁移,但幸运的是 HttpContext 被迁移了。

这可能会产生误导,因为 HttpContext 是调用上下文,但这在 ASP.NET 中有点怪癖,可能是因为偷工减料以提高性能。

您必须删除对 CallContext 的依赖项并在整个过程中使用 HttpContext。

您可以在 Piers7 的 terrific blog post 中阅读更多详细信息。

【讨论】:

  • 谢谢 Nikola,这几乎解释了一切,但是 HttpContext.Current 恰好是跨线程的 null,我正在考虑同时使用 HttpContext 和CallContext、HttpContext 用于从 BeginRequest 到请求结束的常规 ASP.net 请求,以及每当我启动一个新线程时的 CallContext,你怎么看?
  • 我没有看到太多选择。您要么需要 1) 在将方法排队到 ThreadPool 时将所需的所有数据传递给方法,要么 2) 实际发送 HttpContext 并小心处理。您确实链接到了一个 SO question,该问题指出这“不是线程安全的”。此操作是线程安全的,您将发送正确的 HttpContext 实例,但它保存的集合不是线程安全的,因此您需要小心访问它的方式。为了安全起见,也许您可​​以避免将其分配给工作线程中的 HttpContext.Current,并直接使用它,或者至少在工作完成后将其清理。
【解决方案2】:

这是在聊天会话期间解决的。

本质上,它涉及长时间运行的任务,建议使用外部服务(Web 或常规 Windows 服务)作为问题的最佳解决方案。

【讨论】:

  • CacheApplication 是在 Application 的整个生命周期中存储数据的一般位置,我需要一个位置存储 per-request 信息,这可以使用 HttpContext.Current.Items 来完成。然而,正如我上面提到的,这不能用多线程应用程序来完成!
  • @KamyarNazeri,请参阅我的更新答案。在请求开始时实例化它,并在最后销毁它。它是线程安全的。
  • 如果请求在另一个线程/线程完成工作之前结束怎么办?在那种情况下,我需要跟踪每个线程并在最后一个线程结束时以某种方式处理缓存!另一个请求之间的某个地方可能会命中应用程序并将错误的数据写入缓存!当然,为不同的用户/请求使用不同的密钥是另一个需要特别注意的问题!
  • @KamyarNazeri,也许您可​​以扩展您实际上 在做什么 以使其成为必要?
  • 记住 HttpContext.Current 跨线程为空,因此 session 不可能是存储数据的正确位置
【解决方案3】:

第二种方法的线程安全是最好的方法。 这是单音的线程安全版本:

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 { } 块是关键。

【讨论】:

  • 这是完全错误的!锁定不适用于此处。 CallContext 方法都是静态的,并且在当前线程中的调用上下文上进行操作,这意味着它们在每个执行逻辑线程所独有的数据槽上工作,并且它们本身是线程安全的!
  • 我认为从 CallContext 返回的 null 值是因为两个线程同时设置了 callContext 键。并且 [也许] CallContext.SetData() 内部出了点问题......所以锁定将管理它。并且没有性能问题。你可以测试一下。也许空值会消失。顺便说一句,我自己没有测试过。锁定不会锁定字段或属性或其他内容。它锁定了一段代码。我只是认为这是您的第二种方法的更好实施
  • 如果您查看.net 源代码referencesource.microsoft.com/#mscorlib/system/runtime/remoting/…,您会注意到SetData 方法实际上利用Thread.CurrentThread 来获得一个空闲数据槽。所以这里没有并发,也不需要锁定任何东西
  • 好吧,这只是猜测。但是为什么 CallContext 有时会返回 null 呢?你最终的解决方案是什么
  • CallContext 在 Asp.net 应用程序中不是一个好的选择。事实证明,在负载下,ASP.Net 可以将入站请求从其 IO 线程池迁移到其工作进程线程池占用的队列。即您的请求线程在整个 asp 管道中可能不一样。正如 Nikola Radosavljević 指出的那样,您可以在这里阅读更多内容:piers7.blogspot.fr/2005/11/threadstatic-callcontext-and_02.html
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多