繁体   English   中英

C#Parallel.For和UI更新?

[英]c# Parallel.For and UI updating?

我正在尝试实现Parallel.ForEach循环来替换旧的foreach循环,但是我在更新UI时遇到了麻烦(我有一个计数器显示类似“已处理x / y文件”的内容)。 我制作了一个Parallel Forloop示例来说明我的问题(标签未更新)。

using System;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;

namespace FormThreadTest
{
    public partial class Form1 : Form
    {
        private SynchronizationContext m_sync;
        private System.Timers.Timer m_timer;
        private int m_count;

        public Form1()
        {                       
            InitializeComponent();

            m_sync = SynchronizationContext.Current;

            m_count = 0;

            m_timer = new System.Timers.Timer();
            m_timer.Interval = 1000;
            m_timer.AutoReset = true;
            m_timer.Elapsed += new System.Timers.ElapsedEventHandler(m_timer_Elapsed);
            m_timer.Start();
        }

        private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Task.Factory.StartNew(() =>
            {
                m_sync.Post((o) =>
                {
                    label1.Text = m_count.ToString();
                    Application.DoEvents();
                }, null);
            });
        }

        private void button1_Click(object sender, EventArgs e)        
        {     
            Task.Factory.StartNew(() =>
            {
                Parallel.For(0, 25000000, delegate(int i)
                {
                    m_count = i;
                });
            });
        }
    }
}

如果我更改按钮单击事件方法,并添加Thread.Sleep(),似乎为UI更新线程提供了时间来完成其工作:

private void button1_Click(object sender, EventArgs e)        
        {     
            Task.Factory.StartNew(() =>
            {
                Parallel.For(0, 25000000, delegate(int i)
                {
                    m_count = i;
                    Thread.Sleep(10);
                });
            });
        }

有没有办法避免睡眠,还是我需要在那里睡觉? 除非我这样做,否则我的用户界面似乎不会更新标签? 我发现这很奇怪,因为我可以移动应用程序窗口(它不会锁定)-为什么不更新标签,以及如何更改代码以更好地支持Parallel.For(Each)和UI更新?

我一直在寻找解决方案,但似乎找不到任何东西(或者我可能在寻找错误的东西?)。

问候西蒙

由于结果显示在Parallel.ForEach()上,因此我也有更新GUI的类似要求。 不过,我走的路与您截然不同。

public partial class ClassThatUsesParallelProcessing
{
    public event ProcessingStatusEvent StatusEvent;

    public ClassThatUsesParallelProcessing()
    { }

    private void doSomethingInParallel()
    {
        try
        {
            int counter = 0;
            int total = listOfItems.Count;

            Parallel.ForEach(listOfItems, (instanceFromList, state) =>
            {
                // do your work here ...

                // determine your progress and fire event back to anyone who cares ...
                int count = Interlocked.Increment( ref counter );

                int percentageComplete = (int)((float)count / (float)total * 100);
                OnStatusEvent(new StatusEventArgs(State.UPDATE_PROGRESS, percentageComplete));
            }
        }
        catch (Exception ex)
        {

        }
    }
}

这样,您的GUI将具有类似于以下内容的内容:

private void ProcessingStatusEventHandler(object sender, StatusEventArgs e)
{
    try
    {
        if (e.State.Value == State.UPDATE_PROGRESS)
        {
            this.BeginInvoke((ProcessHelperDelegate)delegate
            {
                this.progressBar.Value = e.PercentageComplete;
            }
        }
    }
    catch { }
}

我要在这里说明的唯一一点是,您可以确定何时确定循环的进度是有意义的。 而且,由于这些循环迭代在后台线程上进行,因此您需要将GUI控件更新逻辑编组回您的主(调度)线程上。 这只是一个简单的示例-只要确保您遵循此概念,就可以了。

我的猜测是,计数到2500万(并行!)所需的时间不到一秒钟……因此,在完成计数之前不会触发您的计时器。 如果添加Thread.Sleep ,整个过程将运行得慢得多,以便可以看到更新。

另一方面,您的计时器事件处理程序看起来很凌乱。 您生成了一个线程以将消息发布到您的UI,当您最终进入UI线程时,您调用Application.DoEvents ...为什么? 您应该能够删除任务创建和DoEvents调用。

编辑 :我测试了您发布的程序,并且看到标签更新了两次。 我的电脑需要超过一秒钟的时间才能计算出25m。 我将数字增加到10亿,并且标签多次更新。

Edit2:您可以将计时器处理程序减少为

    private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        m_sync.Post((o) =>
        {
            label1.Text = m_count.ToString();
        }, null);
    }

不幸的是,显示的数字不是当前正在处理的项目数,而是在引发计时器事件时碰巧要处理的项目的索引。 您将必须自己实施计数。 这可以通过使用

Interlocked.Add(ref m_count, 1);

进行平行尝试

//private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)

System.Threading.Tasks.Task.Factory.StartNew(() =>
{
  m_sync.Post((o) =>
  {
    label1.Text = m_count.ToString();
    Application.DoEvents();
  }, null);

  System.Threading.Thread.Sleep(1000;)

}, System.Threading.Tasks.TaskCreationOptions.LongRunning);

但它可能不起作用。 您可能会遇到线程异常,因为winform控件将不允许另一个不是创建者线程的线程对其进行更新。 如果是这种情况,请尝试创建另一个方法,并使用控件本身作为委托进行调用。

eg. label1.Invoke(updateUIHandler);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM