Progress bar in parallel loop invocation

前端 未结 1 1687
孤城傲影
孤城傲影 2020-12-18 03: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

相关标签:
1条回答
  • 2020-12-18 03:33

    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();
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题