c#Parallel.For和UI更新?

时间:2011-01-24 14:26:19

标签: c# winforms user-interface parallel-processing

我正在尝试实现一个Parallel.ForEach循环来替换旧的foreach循环,但我无法更新我的UI(我有一个计数器显示类似'x / y文件已处理')。我制作了一个并行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);
                });
            });
        }

没有办法避免睡眠,或者我需要在那里睡觉吗?看起来我的ui不会更新标签,除非我这样做?我觉得很奇怪,因为我可以移动应用程序窗口(它没有锁定) - 所以为什么标签不会更新,我怎么能改变我的代码以更好地支持Parallel.For(每个)和UI更新?

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

此致 西蒙

3 个答案:

答案 0 :(得分:4)

我有类似的要求更新我的GUI,因为结果出现在Parallel.ForEach()上。不过,我的方式与你的方式截然不同。

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控件更新逻辑编组回主(调度)线程。这只是一个简单的例子 - 只要确保你遵循这个概念就可以了。

答案 1 :(得分:2)

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

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

编辑:我测试了您发布的程序,并看到标签更新两次。我的电脑需要超过一秒才能达到25米。我将这个数字增加到了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);

答案 2 :(得分: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控件不允许另一个不是creater线程的线程更新它。如果是这种情况,请尝试创建另一个方法并使用控件本身作为委托调用。

eg. label1.Invoke(updateUIHandler);