并行循环调用中的进度条

本文关键字:循环 调用 并行 | 更新日期: 2023-09-27 17:50:46

我正在尝试在多线程环境中更新进度条。我知道有很多问题已经涉及到这个问题,但没有一个建议的解决方案对我有效。下面是我的代码的主干:

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.
  }
}

这不起作用(进度条在到达100%时冻结,这是正常的(我们可以参考微软关于这个问题的文章(实际上,这不是一个线程安全的操作方法))。Microsoft站点规定将Parallel.For循环包装到Task例程中,如下所示:

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.
      ..
    }
  });
});

但是,当调试线程直接退出Task作用域时,这就不能正常工作了。

编辑2:

基本上,我的问题分为3部分:Computation.cs(其中DO_Computation暴露),WinForm这是包含进度条的表单,和MainWindow这是包含按钮的表单,当点击打开与进度条的表单。

我不清楚"Task"在这种情况下有什么用。因为它在没有执行任何Parallel.For工作的情况下离开了Task范围

任何想法?

Many Thanks,

编辑3:

我在Noseratio的帮助下升级了我的代码(感谢他)。然而,我有同样的问题,这是代码内的任务是永远不会执行。我的代码现在看起来像:

 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
 之前

());

注意,Do_Computation函数本身是从一个运行BackGroundWorker的Form中调用的。

并行循环调用中的进度条

使用async/await, Progress<T>并观察与CancellationTokenSource相消

值得一读,相关:"Async in 4.5:在Async api中启用进度和取消"。

如果你需要以。net 4.0为目标,但使用VS2012+开发,你仍然可以使用async/await,微软为此提供了Microsoft.Bcl.Async库。

我把一个WinForms的例子放在一起来说明上面的所有内容。它还展示了如何使用ParallelLoopState.Stop():

观察Parallel.For循环的取消
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();
        }
    }
}

更新了,这里是如何在没有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();
        }
    }
}