-->

Parallel.Foreach starts to idle with Invoke [close

2020-05-11 03:34发布

问题:

I have a problem with the Parallel.Foreach loop. It works as it should as long I dont Invoke a method to increase the Progressbar Value of the parent GUI program. Where KeinPapierVersand is a simple List<int> object and EinzelnachweisDruckDatum is a DateTime.

Parallel.ForEach(KeinPapierVersand, partner =>
{
    generate_PCL_nachweis(partner, EinzelnachweisDruckDatum, true, false);
    generate_BGF_Report(partner, EinzelnachweisDruckDatum, false);

    //If the following line is uncommented, the loop starts to idle after about 200 
    // processed Items and will never reach the code after the loop.

    myProgressbar_einzelnachweis_druck.Parent.BeginInvoke(new MethodInvoker(delegate 
    { 
        myProgressbar_einzelnachweis_druck.Value = 
                                         myProgressbar_einzelnachweis_druck.Value + 1; 
    }));
});

These are my first steps with parallelism in C#. I have no clue what's the problem here, there is no exception thrown (had it in a Try/Catch before). Without the invoke of the progressbar the loop ends always without a problem. Why is my logic not working? Where is my error in reasoning? Please help.

EDIT: The problem here is a deadlock situation, perfectly explained below by Aaron. I put the whole workload, which I want to be processed, into a backgroundworker. This works flawless.

回答1:

Most likely, you're calling the Parallel.ForEach method from your main UI thread. Like, in response to a button click or other UI event. Assuming this is the case, then Parallel.ForEach line itself is running on the main UI thread and will block that main thread waiting till all the concurrent work that it's requesting is done.

The way that Invoke() works is that it sends a message to the main UI thread and waits for the message to get processed. When that processing is complete, it will return and progress will continue. In your code, when a thread calls Invoke(), it's going to wait there for Invoke to complete. However, Invoke never will complete because the main thread is blocked by the Parallel.ForEach and can't process the request to update the progress bar till the Parallel.ForEach is complete. It's a type of deadlock.

There are lots of ways to fix this problem. They all essentially amount to getting the Parallel.ForEach off the main thread. One of the most simple ways to do this, if you can here (don't have enough of a code sample to tell) is to use async/await to kick off a task that then calls the Parallel.ForEach and awaits the results. This will free up your main thread to be able to process the progress bar updates.

Once you get that working, you may also want to consider switching to using BeginInvoke here instead of Invoke because BeginInvoke doesn't wait for the main thread to process the message before completing. It's a bit trickier to use because the update order isn't guaranteed and the update could even occur after the ForEach completes. It won't fix your root problem, you have to fix that first, but it'll be more efficient because the Invoke() version will actually slow down your Parallel loop.