Parallel.For 多久调用一次 localInit?

Col*_*nic 5 .net task-parallel-library parallel.foreach

我一直在试验 Parallel.For。特别是支持线程本地数据的重载,例如

public static System.Threading.Tasks.ParallelLoopResult For (long fromInclusive, long toExclusive, System.Threading.Tasks.ParallelOptions parallelOptions, Func localInit, Func body, Action localFinally);

根据文档

为参与循环执行的每个线程调用一次 localInit 委托

但是我认为我下面的例子与它相矛盾

var threads = new ConcurrentBag<int>();
ValueTuple LocalInit()
{
    threads.Add(Thread.CurrentThread.ManagedThreadId);
    return new System.ValueTuple();
}
ValueTuple Body(long i, ParallelLoopState _, ValueTuple state) 
{
    Thread.Sleep(100);
    return state;
}
void LocalFinally(ValueTuple state) { };

Parallel.For(0L, 1000L, new ParallelOptions(), LocalInit, Body, LocalFinally);

Console.WriteLine($"{threads.Count} inits between {threads.Distinct().Count()} threads");
Run Code Online (Sandbox Code Playgroud)

它打印一条消息,例如

13 个线程之间的 79 个初始化

这是怎么回事?

Col*_*nic 3

尝试记录任务 IDTask.CurrentId而不是线程 ID。

\n\n
var threads = new ConcurrentBag<int>();\nvar tasks = new ConcurrentBag<int>();\nValueTuple LocalInit()\n{\n    threads.Add(Thread.CurrentThread.ManagedThreadId);\n    tasks.Add(Task.CurrentId ?? throw new Exception());\n    return new System.ValueTuple();\n}\nValueTuple Body(long i, ParallelLoopState _, ValueTuple state) \n{\n    Thread.Sleep(100);\n    return state;\n}\nvoid LocalFinally(ValueTuple state) { };\n\nParallel.For(0L, 1000L, new ParallelOptions(), LocalInit, Body, LocalFinally);\n\nConsole.WriteLine($"{threads.Count} inits between {threads.Distinct().Count()} threads");\nConsole.WriteLine($"{tasks.Count} inits between {tasks.Distinct().Count()} tasks");\n
Run Code Online (Sandbox Code Playgroud)\n\n

这打印

\n\n
\n

16 个线程之间有 87 个初始化
\n 87 个任务之间有 87 个初始化

\n
\n\n

文档是错误的。他们应该说

\n\n
\n

对于参与循环执行的每个任务,都会调用一次 localInit 委托

\n
\n\n

任务数量可以多于线程数量。始终是线程数\xe2\x89\xa4任务数\xe2\x89\xa4迭代次数

\n

  • 代码也可能因其他原因而阻塞,这可能不受开发人员的控制。这里的要点是,有 87 个 init,而不是 16 个线程。这不是用代码阻塞来解释的。所发生的情况是,16 个不同的线程被重用来执行 87 个任务。正如答案中所解释的,“LocalInit()”为每个任务而不是每个线程调用一次。设计是合理的(当重用线程时必须重新初始化),但应该正确记录。 (3认同)