用UI更新处理并行循环

本文关键字:并行 循环 处理 更新 UI | 更新日期: 2023-09-27 17:54:53

下面有一个更新

我有一个查询,在大约90,000头记录。然后,我想遍历该结果集,以获取检索到的每个标题的详细数据。如果我线性处理的话,大概需要一个半小时。如果我并行化它,我可以在11分钟内完成。然而,没有屏幕更新。我做过很多次多线程应用程序,并且总是成功地完成如下操作:

this.lblStatus.Invoke(new MethodInvoker(() => this.lblStatus.Text = "Updating Standards Docs"));

然而,这似乎真的屏蔽了一个并行循环。对于某些屏幕更新使用该方法,Parallel循环实际上从未完成。所以我需要另一个方法

I've been try:

Task.Factory.StartNew(() =>
            {
                OrderablePartitioner<PayrollHeader> partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
                Parallel.ForEach(partitioner, thisCheck =>
                {
                    Interlocked.Increment(ref iChckNo);
                    lock (_status)
                    {
                        _status.ProcessMsg = "Voucher " + thisCheck.VoucherNumber;
                        _status.ProcessName = thisCheck.EmployeeName;
                        _status.CurrentRec = iChckNo;
                        dtSpan = DateTime.Now.Subtract(dtSpanStart);
                        _status.TimeMsg = string.Format("Elapsed {0}:{1}:{2}", dtSpan.Hours, dtSpan.Minutes, dtSpan.Seconds);
                    }
                    BeginInvoke((Action) (() =>
                    {
                        lblVoucher.Text = _status.ProcessMsg;
                        lblName.Text = _status.ProcessName;
                        lblCount.Text = string.Format("Record {0} of {1}", _status.CurrentRec, _status.TotalRecs);
                        lblTime.Text = _status.TimeMsg;
                        Application.DoEvents();
                    }));
                    thisCheck.GetDetails();
                });
            }).Wait();

等待任务是因为之后我需要对查询执行其他操作,我将最终将其放入ContinueWith语句中,我只需要让屏幕更新工作。

我知道所有关于跨线程损坏,这就是为什么我试图使用Invoker方法…我坚信长时间运行的进程仍然需要让用户知道,这就是我尝试这样做的原因。

顺便说一句,这是一个WinForms应用程序,而不是WPF应用程序。任何帮助都将非常感激…

UPDATE:所以有人想看到更新后的代码,其中包含IProgress。

Status _status = new Status {TotalRecs = query.Count};
            var progress = new Progress<Status>(msg => WriteStatusUpdate(msg));
            Task.Run(() =>
            {
                OrderablePartitioner<PayrollHeader> partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
                Parallel.ForEach(partitioner, thisCheck =>
                {
                    lock (_status)
                    {
                        _status.ProcessMsg = "Voucher " + thisCheck.VoucherNumber;
                        _status.ProcessName = thisCheck.EmployeeName;
                        _status.CurrentRec = ++iChckNo;
                        dtSpan = DateTime.Now.Subtract(dtSpanStart);
                        _status.TimeMsg = string.Format("Elapsed {0}:{1}:{2}", dtSpan.Hours, dtSpan.Minutes, dtSpan.Seconds);
                    }
                    ((IProgress<Status>) progress).Report(_status);
                    thisCheck.GetDetails();
                });
            }).Wait();
 private void WriteStatusUpdate(Status _status)
    {
        lblVoucher.Text = _status.ProcessMsg;
        lblVoucher.Refresh();
        lblName.Text = _status.ProcessName;
        lblName.Refresh();
        lblCount.Text = string.Format("Records {0} of {1}", _status.CurrentRec, _status.TotalRecs);
        lblCount.Refresh();
        lblTime.Text = _status.TimeMsg;
        lblTime.Refresh();
    }

更新屏幕的代码永远不会被调用…

用UI更新处理并行循环

不要尝试从并行循环内部更新UI。这不仅是因为你不能在后台线程中更新UI,还会导致代码的丑陋和不可维护。并行循环应该进行处理。报告应该由其他人来完成。

.NET框架提供了IProgress<接口报告进度和默认实现进度;在其创建线程(例如UI线程)上引发事件或调用委托。这会产生更简单的代码,例如:>

var stopwatch = Stopwatch.StartNew();
var progressImpl=new Progress<Tuple<int,string,string>>(
                     msg=>ReportProgress(msg,stopwatch))
IProgress<Tuple<int,string,string>> progress=progressImpl;
var  partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
Task.Run(()=> Parallel.ForEach(partitioner, thisCheck =>
            {
             .... 
             var msg=Tuple.Create(iChckNo,thisCheck.VoucherNumber,thisCheck.EmployeeName);
             progress.Report(msg);
             ...
            })
        );
 ...
private void ReportProgress(Tuple<int,string,string> msg,Stopwatch stopwatch)
{
    _status.ProcessMsg = "Voucher " + msg.Item2;
    _status.ProcessName = msg.Item3;
    _status.CurrentRec = msg.Item1;
    _status.TimeMsg = string.Format("Elapsed {0:c}", stopwatch.Elapsed);
};

我在这里使用Tuple<int,string,string>而不是更具体的类是非常懒惰的。

从并行循环内部发送的消息将由Progress<T>在UI线程上封送,并且ReportProgress函数将在UI线程上被调用。

转换为IProgress< T>是必要的,因为Publish方法是显式实现的。这是一种安全措施,可以防止程序员对实现本身进行编码。

我无法让IProgress实际工作,所以我最终做的是,这可能不是最好的方法,我把并行。在Task中调用ForEach,在循环中更新公共对象。当Task真正开始时,我坐在一个while循环中,直到它完成,在这个while循环中,我正在更新UI…

bool blDone = false;
int iChckNo = 0;
_status.TotalRecs = query.Count;
Task.Run(() =>
{
    OrderablePartitioner<PayrollHeader> partitioner = Partitioner.Create(query, EnumerablePartitionerOptions.NoBuffering);
    Parallel.ForEach(partitioner, thisCheck =>
    {
        lock (_status)
        {
            iChckNo++;
            _status.ProcessMsg = "Voucher " + thisCheck.VoucherNumber;
            _status.ProcessName = thisCheck.EmployeeName;
            _status.CurrentRec = iChckNo;
            dtSpan = DateTime.Now.Subtract(dtSpanStart);
            _status.TimeMsg = string.Format("Elapsed {0}:{1}:{2}", dtSpan.Hours, dtSpan.Minutes, dtSpan.Seconds);
        }
        thisCheck.GetDetails();
    });
    blDone = true;
});
while (!blDone)
{
    WriteStatusUpdate();
}
further down in the code is
private void WriteStatusUpdate()
{
    lock (_status)
    {
        lblVoucher.Text = _status.ProcessMsg;
        lblName.Text = _status.ProcessName;
        lblCount.Text = string.Format("Records {0} of {1}", _status.CurrentRec, _status.TotalRecs);
        lblTime.Text = _status.TimeMsg;
        Application.DoEvents();
    }
}

再次强调,这可能不是最好的方法,但无论如何都可以完成…