简体   繁体   English

C#Parallel.For和UI更新?

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

I am trying to implement a Parallel.ForEach loop to replace an old foreach loop, but I am having trouble updating my UI (i have a counter showing something like 'x/y files processed'). 我正在尝试实现Parallel.ForEach循环来替换旧的foreach循环,但是我在更新UI时遇到了麻烦(我有一个计数器显示类似“已处理x / y文件”的内容)。 I have made a Parallel Forloop example to illustrate my problem (the label is not being updated). 我制作了一个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;
                });
            });
        }
    }
}

If I change the button click event method, and add a Thread.Sleep() it seems to give time for the UI updating thread to do its job: 如果我更改按钮单击事件方法,并添加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);
                });
            });
        }

Is there no way of avoiding the sleep, or do I need to have it there? 有没有办法避免睡眠,还是我需要在那里睡觉? it seems like my ui won't update the label unless I do? 除非我这样做,否则我的用户界面似乎不会更新标签? which I find strange, since I can move the application window (it doesn't lock up) - so why won't the label update, and how can I alter my code to better support Parallel.For(Each) and UI updates? 我发现这很奇怪,因为我可以移动应用程序窗口(它不会锁定)-为什么不更新标签,以及如何更改代码以更好地支持Parallel.For(Each)和UI更新?

I've been searching for a solution, but I can't seem to find anything (or I maybe searching for the wrong thing?). 我一直在寻找解决方案,但似乎找不到任何东西(或者我可能在寻找错误的东西?)。

Regards Simon 问候西蒙

I have a similar requirement to update my GUI as the results come in on a Parallel.ForEach(). 由于结果显示在Parallel.ForEach()上,因此我也有更新GUI的类似要求。 I went a very different way than you did, though. 不过,我走的路与您截然不同。

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)
        {

        }
    }
}

Your GUI would then have a something similar to the following: 这样,您的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 { }
}

The only point I'm trying to make here is you can determine when it makes sense to determine your progress through you loop. 我要在这里说明的唯一一点是,您可以确定何时确定循环的进度是有意义的。 And, as these loop iterations are on background threads you will need to marshall the GUI control update logic back onto your main (dispatching) thread. 而且,由于这些循环迭代在后台线程上进行,因此您需要将GUI控件更新逻辑编组回您的主(调度)线程上。 This is just a simple example - just make sure you follow the concept and you'll be fine. 这只是一个简单的示例-只要确保您遵循此概念,就可以了。

My guess is that counting to 25 million (in parallel!) takes less than a second... therefore your timer will not be fired before the counting is complete. 我的猜测是,计数到2500万(并行!)所需的时间不到一秒钟……因此,在完成计数之前不会触发您的计时器。 If you add the Thread.Sleep , the whole thing will run much slower so that you can see the updates. 如果添加Thread.Sleep ,整个过程将运行得慢得多,以便可以看到更新。

On the other hand, your timer event handler looks messy. 另一方面,您的计时器事件处理程序看起来很凌乱。 You spawn a thread to post a message to your UI, and when you are finally on your UI thread, you call Application.DoEvents... why? 您生成了一个线程以将消息发布到您的UI,当您最终进入UI线程时,您调用Application.DoEvents ...为什么? You should be able to remove both the task creation and DoEvents call. 您应该能够删除任务创建和DoEvents调用。

Edit : I tested the program you posted, and saw the label update twice. 编辑 :我测试了您发布的程序,并且看到标签更新了两次。 My computer takes more than one second to count to 25m. 我的电脑需要超过一秒钟的时间才能计算出25m。 I increased the number to 1 billion, and the lable updates multiple times. 我将数字增加到10亿,并且标签多次更新。

Edit2: You can reduce the timer handler to Edit2:您可以将计时器处理程序减少为

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

Unfortunately, the number displayed is not the number of items currently processed, but the index of the item that happened to be processed in the moment the timer event is raised. 不幸的是,显示的数字不是当前正在处理的项目数,而是在引发计时器事件时碰巧要处理的项目的索引。 You will have to implement the counting yourself. 您将必须自己实施计数。 This can be done by using 这可以通过使用

Interlocked.Add(ref m_count, 1);

for parallel try 进行平行尝试

//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);

but it might not work. 但它可能不起作用。 u might got thread exception becus winform control won't allow another thread that is not creater thread update it. 您可能会遇到线程异常,因为winform控件将不允许另一个不是创建者线程的线程对其进行更新。 if that is the case, try create another method and use control itself invoke as delegate. 如果是这种情况,请尝试创建另一个方法,并使用控件本身作为委托进行调用。

eg. label1.Invoke(updateUIHandler);

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

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