【问题标题】:Progress bar in parallel loop invocation并行循环调用中的进度条
【发布时间】:2014-03-18 18:15:21
【问题描述】:

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

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 article(确实,这不是线程安全的操作方式))。 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.
      ..
    }
  });
});

但是这并不好用,当调试线程时直接超出了任务范围。

编辑 2:

基本上,我的问题分为 3 个部分:Computation.csDO_Computation 被暴露),WinForm 是包含进度条的表单,MainWindow 是包含按钮的表单单击时打开带有进度条的表单。

我不清楚在这种情况下“任务”的用途是什么。 因为它超出了任务范围而没有执行任何Parallel.For 工作

有什么想法吗?

非常感谢,

编辑 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 的表单调用的。

【问题讨论】:

  • 请格式化代码,使其真正可读。
  • 发现问题出在哪里(代码上部的委托使整个事情在另一个线程上工作。因此,调用在另一个线程上创建的frmWinProg最终导致了僵局。感谢您的帮助,非常感谢

标签: c# .net multithreading task-parallel-library async-await


【解决方案1】:

使用async/awaitProgress&lt;T&gt; 并使用CancellationTokenSource 观察取消。

一本好书,相关:"Async in 4.5: Enabling Progress and Cancellation in Async APIs"

如果您需要面向 .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();
        }
    }
}

【讨论】:

  • 非常感谢,Noseratio。不幸的是,由于以下几个原因,我无法使用您的解决方案:首先,该软件是使用 .NET 4.0 开发的,我也无法下载 Microsoft.Bcl.Async(公司的隐私)。是否可以在没有 async/wait 和 IProgress 例程的情况下执行此操作?非常感谢。
  • @VLT,是的,可以使用Task.ContinueWith 代替await。虽然代码会更庞大,而且您将无法使用线性代码流。
  • 尝试了一切,我相信 .. 它仍然无法正常工作。在任务 => 超出任务范围而不在并行 for 循环中做任何工作的情况下具有相同的行为
  • 非常感谢!但我相信 Progress(以及 IProgress 接口是在 .Net 4.5 中引入的)因此我无法访问它们:(
  • @VLT,确实是,我错过了。检查我的更新以获取替代解决方案。很遗憾你被限制使用Microsoft.Bcl.Async,它有Progress&lt;T&gt;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多