ThreadPool.QueueUserWorkItem vs Parallel.For

时间:2013-12-19 16:06:35

标签: .net multithreading parallel.foreach queueuserworkitem

我正在尝试理解Parralel.ForThreadPool.QueueUserWorkItem之间的差异。

硬件&软件

  • Intel i5(四核)
  • Windows 7 64位教授
  • DotNet 4.5

案例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个工作线程可用。)

3 个答案:

答案 0 :(得分:0)

我认为“活动”线程和“可用”线程之间存在差异。线程池将重用线程,如果它决定需要更多线程,它将开始将可用线程移动到活动线程。如果你想一次启动很多东西,这可能会令人沮丧,因为每个可用的线程可能需要大约2秒的时间来启动。

MSDN Managed Threadpool

  

作为其线程管理策略的一部分,线程池在创建线程之前会延迟。因此,当许多任务在短时间内排队时,在所有任务开始之前可能会有很长的延迟。

如果您要反复运行这些任务或添加任务,您应该会看到其他线程变为活动状态。

答案 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的默认任务调度程序是ThreadPoolTask​​Scheduler:

private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTask​​Scheduler();

让我们看一下ThreadPoolTask​​Scheduler:

    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