set priority for Parallel.For loop

2020-03-03 04:23发布

Ok, here's the situation: My main/UI thread (call it Thread1) is used for acquiring a batch of images from a phsycial document scanner. When a batch has been acquired, a separate "background" thread (call it Thread2) starts up to process and save the images from that batch.

Thread2 (the "background" thread) is using a Parallel.For loop which reduces the image processing/saving time by 70% over a normal For loop. However, it also appears to be maxing out all of my processors so that Thread1 can not start acquiring any more images until the Parallel.For loop completes.

Is there a way to "limit" a Parallel.For loop so that it does not max out my processors? Or to set the processing priority? I tried setting Thread2.Priority = ThreadPriority.Lowest, but this does not appear to affect the loop. Or am I misunderstanding how a Parallel.For loop works? Is it blocking Thread1 somehow?

Here is how I call the Thread2 from a method in Thread1.

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
}

Here is my ThreadWork method:

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条回答
神经病院院长
2楼-- · 2020-03-03 05:12

Is there a way to "limit" a Parallel.For loop so that it does not max out my processors?

Yes, you can add an Options with MaxDegreeOfParallelism=N.

Or to set the processing priority?

No. It is a ThreadPool (borrowed) thread. Don't change its properties. Actually it's a bunch of pool threads.

Or am I misunderstanding how a Parallel.For loop works? Is it blocking Thread1 somehow?

Yes, from the outside Parallel.For(...) is a blocking call. So run it on a separate Task or Backgroundworker, not from the main thread.

查看更多
\"骚年 ilove
3楼-- · 2020-03-03 05:14
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());

   }

Or just:

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;
            });
查看更多
爱情/是我丢掉的垃圾
4楼-- · 2020-03-03 05:19

Without access to the entire application it is difficult to know exactly what is happening here but let's start by breaking down 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
}

So changing Thread2's priority will not make a difference since it is blocked anyway. However, if Thread1 is not blocked, it should still be able to run. Thread1 may not run often, which could be your problem.

The naive approach would be to do something like mess with thread priorities, counts, or add some Thread.Yield() statements. However, the threads from the pool are likely already blocking since they are doing I/O.

Most likely, what you need to do here is refactor your code so that your image loading loop blocks on your main thread's image acquiring using something like System.Threading.WaitHandle or move more of the work the main thread is doing into the image loading. Without refactoring, experience says you will end up with a solution that is tailored to the specific machine you are testing on, under specific running conditions but, when loads change or hardware changes, your "tuning" will be off.

Rework the code so more work is being done inside your Parallel.For workers and block your threads on main thread activity when there is work for the main thread and you will have a solution of which you are proud.

查看更多
三岁会撩人
5楼-- · 2020-03-03 05:23

A crude way would be the MaxDegreeOfParallelism flag in ParallelOptions.

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
}
查看更多
祖国的老花朵
6楼-- · 2020-03-03 05:26

OK, I figured it out! I'm only posting this in case someone ever inadvertantly has this happen to them...

It turns out that the Parallel.For thread was NOT blocking Thread1 (yes, you were all right). HOWEVER, an object in Thread1 was trying to grab a new Thread from the ThreadPool while the loop was crunching away, and thus the "delay" occured. I'm using a 3rd party SDK that allows me to interact with the TWAIN interface and there was an option ScanInNewThread = true that was attempting to grab a new thread each time the user started a new scan (which was occuring while the loop was crunching away). I was able to change this so that a single (but still separate) thread is used throughout an application session instead of grabbing a new thread for each scan batch, and BANG, no more noticeable delay.

SO - the moral of the story:

Existing threads should still function "normally" (with the exception of the thread calling the Parallel.For loop) as long as they are not trying to grab more threads from the ThreadPool while the loop is going.

查看更多
登录 后发表回答