8

我正在使用 .AsParallel().ForAll() 在 ASP.NET 请求的上下文中并行枚举集合。枚举方法依赖于 System.Threading.Thread.CurrentPrincipal。

我可以依赖用于将其 System.Threading.Thread.CurrentPrincipal 设置为处理 ASP.NET 请求的线程的 HttpContext.Current.User 的各个线程,还是我需要自己管理它?

另一种提问方式是 PLINQ 使用的线程是否继承了调用该操作的线程的标识?

4

3 回答 3

11

不,身份不会自动传播到这些工作线程。实际上,如果您正在使用的组件是HttpContext.User您可以做的,那么您可以HttpContext在“主”线程中捕获当前的“环境”实例并将其传播到您的工作线程。看起来像这样:

HttpContext currentHttpContext = HttpContext.Current;

myWorkItems.AsParallel().ForAll(wi =>
{ 
    HttpContext.Current = currentHttpContext;

    try
    {
        // anything called from here out will find/use the context of your original ASP.NET thread
    }
    finally
    {
       // Disassociate the context from the worker thread so that it is not held on to beyond its official lifetime
       HttpContext.Current = null;
    }
});

这是有效的,因为HttpContext.Current它由线程静态支持,因此每个工作线程都将从您的主线程分配一个实例,并且从那时起对其进行的任何工作都会将其视为当前实例。

现在,您必须意识到,HttpContext它及其相关的类并非设计为线程安全的,所以这有点像 hack。如果您只是从属性中读取,这不是一个真正的问题。如果您不使用依赖的组件,HttpContext.Current那么不设置它会更“干净”,而是currentHttpContext直接在工作程序中使用捕获的变量。

最后,如果您真正需要的只是将当前主体传播到工作线程,那么您可以使用相同的方法来做到这一点:

Principal logicalPrincipal = Thread.CurrentPrincipal;

myWorkItems.AsParallel().ForAll(wi =>
{ 
    Principal originalWorkerThreadPrincipal = Thread.CurrentPrincipal;
    Thread.CurrentPrincipal = logicalPrincipal;

    try
    {
        // anything called from here out will find the principal from your original thread
    }
    finally
    {
       // Revert to the original identity when work is complete
       Thread.CurrentPrincipal = originalWorkerThreadPrincipal;
    }
});
于 2011-12-04T20:38:39.893 回答
3

这是背后的实现CurrentPrincipal

public static IPrincipal CurrentPrincipal
{
    get
    {
        lock (CurrentThread)
        {
            IPrincipal threadPrincipal = CallContext.Principal;
            if (threadPrincipal == null)
            {
                threadPrincipal = GetDomain().GetThreadPrincipal();
                CallContext.Principal = threadPrincipal;
            }
            return threadPrincipal;
        }
    }
    set { CallContext.Principal = value; }
}

所有新创建的线程都将具有 null 并且将从应用程序域中获取。所以应该没问题。然而,你需要小心文化。它不会从启动线程派生。请参阅:并行编程、PLINQ 和全球化

于 2011-11-30T09:33:23.787 回答
1

将Principal通过.AsParallel()边界传递时需要注意的一件微妙的事情:您的序列在哪里实现?

这不是.ForAll()的问题,但考虑另一种情况:

var result = items.AsParallel().Select(MyTransform);

然后您将结果传递到其他地方,以便它跨越线程边界(这很可能,例如,如果您从 WCF 操作方法中返回它)。

在这种情况下,当MyTransform被应用时,Thread. CurrentPrincipal值可能包含意外的内容。

因此,这里的解决方法是在现场实现查询(通过调用.ToArray().ToList()等)

于 2015-02-04T13:42:14.683 回答