Progress bar in parallel loop invocation

2019-01-25 13:10发布

问题:

I am trying to update a progressbar in a multithreaded environment. I know that a lot of questions already treat that question but none of the proposed solution have worked for me. Here is the backbone of my code :

public static void DO_Computation(//parameters) {
  //Intialisation of parameters

  Parallel.For(struct initialisation with local data) {
    //business logic
    //Call to update_progressbar (located in an another class, as the DO_Computation function is in Computation.cs class (not deriving from Form). 
    WinForm.Invoke((Action)delegate {Update_Progress_Bar(i);}); //WinForm is a class that exposes the  progressbar.
  }
}

This is not working (the progressbar is freezing when arriving at 100%, which is normal (we can refer to the microsoft article in this matter (indeed, this is not a thread-safe operating method)). The Microsoft site stiplates to wrap the Parallel.For loop into a Task routine as follows:

public static void DO_Computation(//parameters) {
  //Intialisation of parameters
  Task.Factory.StartNew(() =>
  {
    Parallel.For(struct initialosation with local data) {
      //business logic
      //Call to update_progressbar (ocated in an another class, as the DO_Computation function is in Computation.cs class (not deriving from Form). 
      WinForm.Invoke((Action)delegate {Update_Progress_Bar(i);}); //WinForm is a class that exposes the  progressbar.
      ..
    }
  });
});

However this is not working as well, when debugging the thread is getting out of the Task scope directly.

EDIT 2:

Basically, my problem is divided in 3 parts: Computation.cs (where DO_Computation is exposed), WinForm which is the form containing the progress bar, and MainWindow which is the form that contains the button which when clicked opens the form with the progress bar.

I do not clearly understand what is the use of "Task" in this case. Because it is going out of the Task scope without performing any Parallel.For work

Any ideas?

Many Thanks,

EDIT 3:

I upgraded my code with the help of Noseratio (thans a lot to him). However I have the same problem which is the code inside task is never executed. My code now looks like :

DoComputation method //Some Initilasations here Action enableUI = () => { frmWinProg.SetProgressText("Grading Transaction..."); frmWinProg.ChangeVisibleIteration(true); }; Action<Exception> handleError = (ex) => { // error reporting MessageBox.Show(ex.Message); }; var cts = new CancellationTokenSource(); var token = cts.Token; Action cancel_work = () => { frmWinProg.CancelTransaction(); cts.Cancel(); }; var syncConext = SynchronizationContext.Current; Action<int> progressReport = (i) => syncConext.Post(_ => frmWinProg.SetIteration(i,GrpModel2F.NumOfSim, true), null); var task = Task.Factory.StartNew(() => { ParallelLoopResult res = Parallel.For<LocalDataStruct>(1,NbSim, options, () => new DataStruct(//Hold LocalData for each thread), (iSim, loopState, DataStruct) => //Business Logic if (token.IsCancellationRequested) { loopState.Stop(); } progressReport(iSim); //Business Logic return DataStruct; }, (DataStruct) => //Assiginig Results; });//Parallel.For end }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default); task.ContinueWith(_ => { try { task.Wait(); } catch (Exception ex) { while (ex is AggregateException && ex.InnerException != null) ex = ex.InnerException; handleError(ex); } enableUI(); }, TaskScheduler.FromCurrentSynchronizationContext

());

Note that the Do_Computation function is itself called from a Form that runs a BackGroundWorker on it.

回答1:

Use async/await, Progress<T> and observe cancellation with CancellationTokenSource.

A good read, related: "Async in 4.5: Enabling Progress and Cancellation in Async APIs".

If you need to target .NET 4.0 but develop with VS2012+ , you still can use async/await, Microsoft provides the Microsoft.Bcl.Async library for that.

I've put together a WinForms example illustrating all of the above. It also shows how to observe cancellation for Parallel.For loop, using ParallelLoopState.Stop():

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication_22487698
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        IEnumerable<int> _data = Enumerable.Range(1, 100);
        Action _cancelWork;

        private void DoWorkItem(
            int[] data,
            int item,
            CancellationToken token,
            IProgress<int> progressReport,
            ParallelLoopState loopState)
        {
            // observe cancellation
            if (token.IsCancellationRequested)
            {
                loopState.Stop();
                return;
            }

            // simulate a work item
            Thread.Sleep(500);

            // update progress
            progressReport.Report(item);
        }

        private async void startButton_Click(object sender, EventArgs e)
        {
            // update the UI
            this.startButton.Enabled = false;
            this.stopButton.Enabled = true;

            try
            {
                // prepare to handle cancellation
                var cts = new CancellationTokenSource();
                var token = cts.Token;

                this._cancelWork = () =>
                {
                    this.stopButton.Enabled = false;
                    cts.Cancel();
                };

                var data = _data.ToArray();
                var total = data.Length;

                // prepare the progress updates
                this.progressBar.Value = 0;
                this.progressBar.Minimum = 0;
                this.progressBar.Maximum = total;

                var progressReport = new Progress<int>((i) =>
                {
                    this.progressBar.Increment(1);
                });

                // offload Parallel.For from the UI thread 
                // as a long-running operation
                await Task.Factory.StartNew(() =>
                {
                    Parallel.For(0, total, (item, loopState) =>
                        DoWorkItem(data, item, token, progressReport, loopState));
                    // observe cancellation
                    token.ThrowIfCancellationRequested();
                }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            // update the UI
            this.startButton.Enabled = true;
            this.stopButton.Enabled = false;
            this._cancelWork = null;
        }

        private void stopButton_Click(object sender, EventArgs e)
        {
            if (this._cancelWork != null)
                this._cancelWork();
        }
    }
}

Updated, here's how to do the same without async/await:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication_22487698
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        IEnumerable<int> _data = Enumerable.Range(1, 100);
        Action _cancelWork;

        private void DoWorkItem(
            int[] data,
            int item,
            CancellationToken token,
            Action<int> progressReport,
            ParallelLoopState loopState)
        {
            // observe cancellation
            if (token.IsCancellationRequested)
            {
                loopState.Stop();
                return;
            }

            // simulate a work item
            Thread.Sleep(500);

            // update progress
            progressReport(item);
        }

        private void startButton_Click(object sender, EventArgs e)
        {
            // update the UI
            this.startButton.Enabled = false;
            this.stopButton.Enabled = true;

            Action enableUI = () =>
            {
                // update the UI
                this.startButton.Enabled = true;
                this.stopButton.Enabled = false;
                this._cancelWork = null;
            };

            Action<Exception> handleError = (ex) =>
            {
                // error reporting
                MessageBox.Show(ex.Message);
            };

            try
            {
                // prepare to handle cancellation
                var cts = new CancellationTokenSource();
                var token = cts.Token;

                this._cancelWork = () =>
                {
                    this.stopButton.Enabled = false;
                    cts.Cancel();
                };

                var data = _data.ToArray();
                var total = data.Length;

                // prepare the progress updates
                this.progressBar.Value = 0;
                this.progressBar.Minimum = 0;
                this.progressBar.Maximum = total;

                var syncConext = SynchronizationContext.Current;

                Action<int> progressReport = (i) =>
                    syncConext.Post(_ => this.progressBar.Increment(1), null);

                // offload Parallel.For from the UI thread 
                // as a long-running operation
                var task = Task.Factory.StartNew(() =>
                {
                    Parallel.For(0, total, (item, loopState) =>
                        DoWorkItem(data, item, token, progressReport, loopState));
                    // observe cancellation
                    token.ThrowIfCancellationRequested();
                }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

                task.ContinueWith(_ => 
                {
                    try
                    {
                        task.Wait(); // rethrow any error
                    }
                    catch (Exception ex)
                    {
                        while (ex is AggregateException && ex.InnerException != null)
                            ex = ex.InnerException;
                        handleError(ex);
                    }
                    enableUI();
                }, TaskScheduler.FromCurrentSynchronizationContext());
            }
            catch (Exception ex)
            {
                handleError(ex);
                enableUI();
            }
        }

        private void stopButton_Click(object sender, EventArgs e)
        {
            if (this._cancelWork != null)
                this._cancelWork();
        }
    }
}