我们的系统使用写入的每条消息写入ManagedThreadID,多年来我们一直使用它来帮助区分日志中的许多工作单元。到目前为止,非常好。
现在,我们开始使用任务并行库并注意到一个有趣的效果:
public static void Main(string[] args) {
WriteLine("BEGIN");
Parallel.For(0, 32, (index) => {
WriteLine(" Loop " + index.ToString());
});
WriteLine("END");
}
输出类似于:
ThreadID=1, Message=BEGIN
ThreadID=1, Message= Loop 0
ThreadID=3, Message= Loop 16
ThreadID=3, Message= Loop 17
...
ThreadID=4, Message= Loop 4
ThreadID=4, Message= Loop 5
ThreadID=1, Message= Loop 8
ThreadID=1, Message= Loop 9
ThreadID=1, Message= Loop 10
ThreadID=3, Message= Loop 21
ThreadID=4, Message= Loop 6
...
ThreadID=3, Message= Loop 24
ThreadID=3, Message= Loop 25
ThreadID=1, Message= Loop 11
ThreadID=1, Message= Loop 12
ThreadID=1, Message= Loop 13
ThreadID=1, Message= Loop 31
ThreadID=3, Message= Loop 26
...
ThreadID=3, Message= Loop 30
ThreadID=1, Message=END
您会注意到主线程的ThreadID(标记为“BEGIN”)有时会在循环线程中被回收。
我的问题是:这可能发生在其他任何地方 - 例如线程池或使用任务并行库的其他功能吗?我花了很多时间试图找出引发行为的其他方法而不是。
这里的问题是,如果我们不再依赖ThreadID(我们有许多工具可以依赖这种行为),那么我们将避免使用Parallel.For。但如果问题以其他方式表现出来,我们需要弄清楚如何避免它们 UNTIL 我们改进了我们的日志策略和工具支持。
如果有其他方法可以激发这种行为,我想了解它,以便我可以确定我们的任何使用是否满足这些条件,以便我们可以相应地进行纠正。更为棘手的是,我可以获得一个示例程序来激发行为并研究我们工具中的任何副作用。
答案 0 :(得分:3)
Parallel.For确实在调用线程上运行一个worker任务。理由是,由于调用线程必须等到并行循环完成,因此它也可以参与并行操作。
就任务并行库的其他功能而言,阻塞的方法通常会使用调用线程。因此,Parallel.For,Parallel.ForEach,Parallel.Invoke和阻塞PLINQ查询都将重用调用线程作为其中一个worker。另一方面,只是“启动”某些工作并立即返回的操作 - 如Task.Factory.StartNew,Threadpool.QueueUserWorkItem和非阻塞PLINQ查询 - 无法使用调用线程。
作为一种解决方法,您可以在任务中运行Parallel.For并等待任务:
public static void Main(string[] args) {
WriteLine("BEGIN");
Task.Factory.StartNew(() =>
Parallel.For(0, 32, (index) => {
WriteLine(" Loop " + index.ToString());
})
).Wait();
WriteLine("END");
}
警告:如果从ThreadPool线程调用Task.Factory.StartNew(),则上述解决方法将无效。在这种情况下,Wait调用可能最终在调用ThreadPool线程上内联执行任务。
答案 1 :(得分:0)
你不得不使用TPL来避免这种行为
听起来你需要找到一种不同的方法来识别除ThreadID之外的工作单元,如果你不能重写代码以传递某种类型,可能会使用logical call context来传递有关当前线程的信息。标识符