为Parallel.For循环设置优先级

时间:2012-10-26 14:37:33

标签: c# task-parallel-library

好的,情况就是这样:我的主要/ UI线程(称之为Thread1)用于从phsycial文档扫描程序中获取一批图像。获取批次后,会启动一个单独的“后台”线程(称为Thread2)来处理并保存该批次中的图像。

Thread2(“后台”线程)正在使用Parallel.For循环,与正常For循环相比,可将图像处理/保存时间缩短70%。但是,它似乎也最大化了我的所有处理器,因此在Parallel.For循环完成之前,Thread1无法再开始获取任何图像。

有没有办法“限制”Parallel.For循环,以便它不会最大化我的处理器?或者设置处理优先级?我尝试设置Thread2.Priority = ThreadPriority.Lowest,但这似乎不会影响循环。或者我误解了Parallel.For循环是如何工作的?是以某种方式阻止了Thread1吗?

以下是我如何从Thread1中的方法调用Thread2。

public void SaveWithSettings(bool save) // method in Thread1
{
    ....
    Thread thr = new Thread(ThreadWork); // creating new thread (Thread 2)
    thr.Priority = ThreadPriority.Lowest; // does nothing?
    thr.Start(new SaveContainer(sc)); // pass a copy as paramater

    // misc stuff to make scanning possible again
    numBgw++;
    twain.RemoveAllImages(); // clear images
    imagelist.Clear(); // clear imagelist images
    .... // etc. this all appears to process fine while Thread2 is processing
}

这是我的ThreadWork方法:

private void ThreadWork(object data) // executing in Thread2
{
    SaveContainer sc = data as SaveContainer; // holds images

    bool[] blankIndex = new bool[sc.imagelist.Count]; // to use in Parallel.For loop
    for (int i = 0; i < sc.imagelist.Count; i++)
        blankIndex[i] = false; // set default value to false (not blank)

    Parallel.For(0, sc.imagelist.Count, i => // loop to mark blank images
    {
        bool x = false; // local vars make loop more efficient
        x = sc.IsBlankImage((short)i); // check if image at index i is blank
        blankIndex[i] = x; // set if image is blank
    }
    .... // other image processing steps
}

5 个答案:

答案 0 :(得分:5)

public static void PriorityParallelForeach<T>(this IEnumerable<T> source, Action<T> action, ThreadPriority threadPriority, int? maxDegreeOfParallelism = null)
   {
       if (maxDegreeOfParallelism == null || maxDegreeOfParallelism<1)
       {
           maxDegreeOfParallelism = Environment.ProcessorCount;
       }

       var blockingQueue = new BlockingCollection<T>(new ConcurrentQueue<T>(source));
       blockingQueue.CompleteAdding();

        var tasks = new List<Task>() ;

        for (int i = 0; i < maxDegreeOfParallelism; i++)
        {
            tasks.Add(Task.Factory.StartNew(() =>
             {
                 while (!blockingQueue.IsCompleted)
                 {
                     T item;
                     try
                     {
                         item = blockingQueue.Take();
                     }
                     catch (InvalidOperationException)
                     {
                         // collection was already empty
                         break;
                     }

                     action(item);
                 }
             }, CancellationToken.None,
                  TaskCreationOptions.None,
                  new PriorityScheduler(threadPriority)));
        }

        Task.WaitAll(tasks.ToArray());

   }

或者只是:

Parallel.ForEach(testList, item =>
            {

                var priviousePrio = Thread.CurrentThread.Priority;
                // Set your desired priority
                Thread.CurrentThread.Priority = ThreadPriority.Lowest;

                TestCalc(item);

                //Reset priviouse priority of the TPL Thread
                Thread.CurrentThread.Priority = priviousePrio;
            });

答案 1 :(得分:2)

  

有没有办法“限制”一个Parallel.For循环,以便它不会最大化我的处理器?

是的,您可以添加MaxDegreeOfParallelism = N的选项。

  

或者设置处理优先级?

没有。它是一个ThreadPool(借用)线程。不要改变它的属性。实际上它是一堆池线程。

  

或者我误解了Parallel.For循环是如何工作的?是以某种方式阻止了Thread1吗?

是的,来自外部的Parallel.For(...)是阻止来电。因此,在单独的Task或Backgroundworker上运行它,而不是从主线程运行它。

答案 2 :(得分:2)

粗略的方式是ParallelOptions中的MaxDegreeOfParallelism标志。

var Options = new ParallelOptions();

// Keep one core/CPU free...
Options.MaxDegreeOfParallelism = Environment.ProcessorCount - 1;

Paralle.For(0, sc.imagelist.Count, Options, i => // loop to mark blank images
{
    bool x = false; // local vars make loop more efficient
    x = sc.IsBlankImage((short)i); // check if image at index i is blank
    blankIndex[i] = x; // set if image is blank
}

答案 3 :(得分:0)

如果不能访问整个应用程序,很难确切知道这里发生了什么,但让我们先打破Parallel.For:

private void ThreadWork(object data) // executing in Thread2
{
    // Thread2 running here
    SaveContainer sc = data as SaveContainer; // holds images

    bool[] blankIndex = new bool[sc.imagelist.Count]; // to use in Parallel.For loop
    for (int i = 0; i < sc.imagelist.Count; i++)
        blankIndex[i] = false; // set default value to false (not blank)

    // Thread2 blocks on this call
    Paralle.For(0, sc.imagelist.Count, i => // loop to mark blank images
    {
        // Thread from the pool is running here (NOT Thread2)!!!

        bool x = false; // local vars make loop more efficient
        x = sc.IsBlankImage((short)i); // check if image at index i is blank
        blankIndex[i] = x; // set if image is blank
    }
    // Thread2 resumes running here

    .... // other image processing steps
}

因此,改变Thread2的优先级不会产生任何影响,因为它无论如何都会被阻止。但是,如果未阻止Thread1,它仍应能够运行。 Thread1可能无法经常运行,这可能是您的问题。

天真的方法是做一些像乱七八糟的线程优先级,计数或添加一些Thread.Yield()语句。但是,池中的线程可能已经阻塞,因为它们正在执行I / O.

最有可能的是,你需要做的是重构你的代码,以便你的图像加载循环阻止主线程的图像获取使用类似System.Threading.WaitHandle的东西或将主线程正在做的更多工作移动到图像加载。如果没有重构,经验表明,在最终的运行条件下,您将最终获得针对您正在测试的特定机器量身定制的解决方案,但是当负载发生变化或硬件发生变化时,您的“调整”将会关闭。

重新编写代码,以便在Parallel.For工作中完成更多工作,并在主线程工作时阻塞主线程活动上的线程,并且您将拥有一个令您自豪的解决方案。

答案 4 :(得分:0)

好的,我明白了!我只是张贴这个以防万一有人无意中发生了这种情况......

事实证明Parallel.For线程没有阻止Thread1(是的,你没事)。然而,Thread1中的一个对象试图从循环移动的Thread中抓取一个新的ThreadPool,因此发生了“延迟”。我正在使用第三方SDK,它允许我与TWAIN接口进行交互,并且每次用户开始新扫描时都有一个选项ScanInNewThread = true试图获取新线程(这是在循环时发生的)正在嘎吱嘎吱地走了。我能够改变这一点,以便在整个应用程序会话中使用单个(但仍然是单独的)线程,而不是为每个扫描批次抓取一个新线程,并且BANG,没有更明显的延迟。

所以 - 这个故事的寓意:

现有线程仍然应该“正常”运行(调用Parallel.For循环的线程除外),只要它们在循环开始时不试图从ThreadPool获取更多线程