我正在尝试理解Parralel.For
和ThreadPool.QueueUserWorkItem
之间的差异。
硬件&软件
案例1代码:ThreadPool
for (int index = 0; index < 5; index++)
{
ThreadPool.QueueUserWorkItem((indexParam) =>
{
int threadID = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
BeginInvoke((Action)delegate { listBox1.Items.Add("Completed " + indexParam.ToString() + " using thread " + threadID.ToString() + " (" + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString("000") + ")"); });
}, index);
}
输出:
使用主题10(45.871)完成0 使用第11号线(45.875)完成1 使用12号线(45.875)完成2 使用第13号线(45.875)完成3 使用第10号线(46.869)
完成4
案例2代码:Parallel.For
ParallelLoopResult result = Parallel.For(0, 5, (int index, ParallelLoopState loopState) =>
{
int threadID = Thread.CurrentThread.ManagedThreadId;
Thread.Sleep(1000);
BeginInvoke((Action)delegate { listBox1.Items.Add("Completed " + index.ToString() + " using thread " + threadID.ToString() + " (" + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString("000") + ")"); });
});
输出:
使用主题10(16.923)完成0 使用线程11(16.925)完成1 使用线程12(16.925)完成2 使用线程13(16.926)完成3 使用主题14(16.926)
完成4
问题:
从案例1中的结果看,似乎只有四个线程处于活动状态,之后第一个空闲线程用于完成最终任务。在案例2中,出现五个线程立即专用并同时执行。
为什么QueueUserWorkItem的线程处理不使用第五个线程,比如并行类?
(ThreadPool.GetAvailableThreads(...)
确认1023个工作线程可用。)
答案 0 :(得分:0)
我认为“活动”线程和“可用”线程之间存在差异。线程池将重用线程,如果它决定需要更多线程,它将开始将可用线程移动到活动线程。如果你想一次启动很多东西,这可能会令人沮丧,因为每个可用的线程可能需要大约2秒的时间来启动。
作为其线程管理策略的一部分,线程池在创建线程之前会延迟。因此,当许多任务在短时间内排队时,在所有任务开始之前可能会有很长的延迟。
如果您要反复运行这些任务或添加任务,您应该会看到其他线程变为活动状态。
答案 1 :(得分:0)
http://msdn.microsoft.com/en-us/library/system.threading.threadpool.setminthreads%28v=vs.110%29.aspx
在这种情况下,ThreadPool min worker threads默认为4 - 这解释了为什么只使用了4个线程。 文档指出ThreadPool可以决定在达到最小值后是否创建更多线程。
当将最小工作线程数设置为5时,将创建5个线程,并按预期同时完成所有任务。
答案 2 :(得分:0)
所有并行任务都在多个线程中完成,这意味着,线程是并行任务的基本单元。 所以,我认为线程池比TPL更有效。 为什么? 由于TPL的默认任务调度程序是ThreadPoolTaskScheduler:
private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler();
让我们看一下ThreadPoolTaskScheduler:
protected internal override void QueueTask(Task task)
{
if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
{
new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)
{
IsBackground = true
}.Start(task);
return;
}
bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) != TaskCreationOptions.None;
ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
}
然后,让我们看看threadpool
:
internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
ThreadPool.EnsureVMInitialized();
try
{
}
finally
{
ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
}
}
好的......让我们看看我们的另一个选择:
public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object state)
{
StackCrawlMark stackCrawlMark = StackCrawlMark.LookForMyCaller;
return ThreadPool.QueueUserWorkItemHelper(callBack, state, ref stackCrawlMark, false);
}
OK..let挖掘更多:
private static bool QueueUserWorkItemHelper(WaitCallback callBack, object state, ref StackCrawlMark stackMark, bool compressStack)
{
bool result = true;
if (callBack != null)
{
ThreadPool.EnsureVMInitialized();
try
{
return result;
}
finally
{
QueueUserWorkItemCallback callback = new QueueUserWorkItemCallback(callBack, state, compressStack, ref stackMark);
ThreadPoolGlobals.workQueue.Enqueue(callback, true);
result = true;
}
}
throw new ArgumentNullException("WaitCallback");
}
现在,我们发现了同样的观点。 所以,哪个更好,这是你的选择。
这就是为什么我从不使用TPL
而是直接使用threadpool
。