[更新]
实际上,我的原始答案(在这篇文章的底部)似乎有部分错误!
我编写了一个小测试程序,用于测试从线程和任务、ThreadPool 线程以及 Parallel.For 中的线程将数据存储在 CallContext 中的场景。在 Tasks 测试和 ThreadPool 测试中,当同一个线程(由 ManagedThreadId 确定)被重用时,CallContext 中存储的数据不会再次出现。但是,在 Parallel.For 的情况下,当重用同一线程(由 ManagedThreadId 确定)时,会再次看到存储在 CallContext 中的数据。我觉得这很有趣。我不确定这些结果是否符合预期,或者我的程序是否有问题。
要尝试每种情况,只需取消注释所需的测试功能。
您将看到 Tasks 和 ThreadPool 线程在线程的后续重用中永远不会遇到 CallContext 数据,而 Parallel.For 线程会遇到 CallContext 数据。
Parallel.For 的行为似乎不一致。当我运行 Parallel.For 案例时,我可以看到给定线程在重用该线程时不一定总是找到 CallContext 数据。例如,这是程序一次运行的输出(未注释 UseParallelFor):
thread [9] - no CallContext value
thread [10] - no CallContext value
thread [11] - no CallContext value
thread [12] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext <-- this is expected as this is the main thread
thread [10] - no CallContext value
thread [13] - no CallContext value
thread [11] - no CallContext value
thread [12] - no CallContext value
thread [14] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [11], cc.thread [11] - value already in CallContext
thread [13] - no CallContext value
thread [15] - no CallContext value
thread [12], cc.thread [12] - value already in CallContext
thread [16] - no CallContext value
thread [14] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext
thread [10] - no CallContext value
thread [17] - no CallContext value
thread [13], cc.thread [13] - value already in CallContext
thread [15] - no CallContext value
thread [11] - no CallContext value
thread [12] - no CallContext value
thread [14], cc.thread [14] - value already in CallContext
thread [18] - no CallContext value
thread [16] - no CallContext value
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [13] - no CallContext value
thread [15], cc.thread [15] - value already in CallContext
thread [11], cc.thread [11] - value already in CallContext
thread [17] - no CallContext value
thread [19] - no CallContext value
thread [18] - no CallContext value
thread [16], cc.thread [16] - value already in CallContext
thread [14] - no CallContext value
thread [20] - no CallContext value
thread [12], cc.thread [12] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [21] - no CallContext value
thread [15] - no CallContext value
thread [11], cc.thread [11] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [13], cc.thread [13] - value already in CallContext
thread [19] - no CallContext value
thread [22] - no CallContext value
thread [18], cc.thread [18] - value already in CallContext
thread [16] - no CallContext value
thread [20] - no CallContext value
thread [14], cc.thread [14] - value already in CallContext
thread [12], cc.thread [12] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [23] - no CallContext value
thread [15], cc.thread [15] - value already in CallContext
thread [21] - no CallContext value
thread [11], cc.thread [11] - value already in CallContext
thread [17] - no CallContext value
thread [13], cc.thread [13] - value already in CallContext
thread [19], cc.thread [19] - value already in CallContext
thread [22] - no CallContext value
thread [16], cc.thread [16] - value already in CallContext
thread [18] - no CallContext value
thread [24] - no CallContext value
thread [20], cc.thread [20] - value already in CallContext
thread [14], cc.thread [14] - value already in CallContext
thread [12], cc.thread [12] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10] - no CallContext value
thread [15], cc.thread [15] - value already in CallContext
thread [21], cc.thread [21] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [13], cc.thread [13] - value already in CallContext
thread [22], cc.thread [22] - value already in CallContext
thread [18], cc.thread [18] - value already in CallContext
thread [16], cc.thread [16] - value already in CallContext
thread [14], cc.thread [14] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [15], cc.thread [15] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [18], cc.thread [18] - value already in CallContext
thread [16], cc.thread [16] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [17], cc.thread [17] - value already in CallContext
thread [18], cc.thread [18] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
thread [9], cc.thread [9] - value already in CallContext
thread [10], cc.thread [10] - value already in CallContext
如您所见,一旦在重用对象中找到一个值,它就不会永远保留在 CallContext 中。在某些情况下,对于给定线程的几次迭代,在 CallContext 中找不到该值,因此将其添加。然后迭代将报告找到该值。然后,也许,下一次迭代会说找不到该值。
结果告诉我,对于在线程重用之间清除给定线程的数据,您不应依赖 CallContext 中保持完整的数据。他们还告诉我,在 Parallel.For 的情况下,您不应该依赖在同一线程的重用之间清除 CallContext。
这是我的测试程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Remoting.Messaging;
namespace CallContextTest
{
class Program
{
static void Main(string[] args)
{
//UseTasks();
//UseThreadPool();
UseParallelFor();
Console.ReadKey();
}
public static void UseThreadPool()
{
int totalThreads = 100;
CountdownEvent finish = new CountdownEvent(totalThreads);
for (int i = 0; i < totalThreads; i++)
{
int ii = i;
ThreadPool.QueueUserWorkItem(x =>
{
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
object o = CallContext.GetData("threadid");
if (o == null)
{
//Always gets here.
Console.WriteLine("thread [{0}] - no CallContext value", id);
CallContext.SetData("threadid", id);
}
else
{
//Never gets here.
Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id);
}
Thread.Sleep(1000);
finish.Signal();
});
}
finish.Wait();
}
public static void UseTasks()
{
int totalThreads = 100;
TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
Task task = null;
Task[] allTasks = new Task[totalThreads];
for (int i = 0; i < totalThreads; i++)
{
int ii = i;
task = Task.Factory.StartNew(() =>
{
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
object o = CallContext.GetData("threadid");
if (o == null)
{
//Always gets here.
Console.WriteLine("thread [{0}] - no CallContext value", id);
CallContext.SetData("threadid", id);
}
else
{
//Never gets here.
Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id);
}
Thread.Sleep(1000);
}, taskCreationOpt);
allTasks[i] = task;
}
Task.WaitAll(allTasks);
}
public static void UseParallelFor()
{
int totalThreads = 100;
Parallel.For(0, totalThreads, i =>
{
int ii = i;
int id = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
object o = CallContext.GetData("threadid");
if (o == null)
{
//Sometimes gets here.
Console.WriteLine("thread [{0}] - no CallContext value", id);
CallContext.SetData("threadid", id);
}
else
{
//Sometimes gets here as threads are reused.
Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id);
}
Thread.Sleep(1000);
});
}
}
}
注意,鉴于上述测试程序和我的新 cmets,在这个答案的顶部,我最初的讨论似乎是错误的。从我的测试来看,在 Tasks 和 ThreadPool 线程的情况下,存储在 CallContext 中的数据似乎在后续重复使用相同的线程 id 时不可用。但是,在 Parallel.For 的情况下,存储在 CallContext 中的数据似乎可用于同一线程的重用。
结束更新后忽略所有内容。
[结束更新]
我当然不是 TPL 专家,但我最近一直在研究 CallContext.SetData(和 LogicalSetData),所以我对 CallContext 的工作原理有了一些了解。 SO上有更好的人来描述在TPL的“上下文”中CallContext数据可能会或可能不会发生的事情。
我对 CallContext.SetData 工作原理的理解是,当线程消失时,“数据”将被清除。因此,如果您创建一个新线程,并且在该线程上执行时,您使用 CallContext.SetData 存储一些数据,那么当线程终止时数据将消失。如果您使用的是 ThreadPool 线程,则该线程永远不会死掉(嗯,也许 never 太强了),因此通过 CallContext.SetData 存储的数据在下一次在 (重用)线程。
我的理解也是Task Parallel Library在内部使用了ThreadPool,所以当再次使用底层的ThreadPool线程时,任何通过CallContext.SetData存储的数据可能仍然存在。
编写一个或多个小测试应该很容易,以查看将数据放入 CallContext 时会发生什么,然后检查它是否存在于同一线程的后续使用中。