[英]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.