c#并行。用于和UI更新?
我试图实现一个Parallel.ForEach循环来替换旧的c#并行。用于和UI更新?,c#,winforms,user-interface,parallel-processing,C#,Winforms,User Interface,Parallel Processing,我试图实现一个Parallel.ForEach循环来替换旧的ForEach循环,但我在更新UI时遇到了问题(我有一个计数器显示类似于“x/y文件已处理”)。我制作了一个并行的Forloop示例来说明我的问题(标签没有更新) 如果我更改button click事件方法,并添加一个Thread.Sleep(),则UI更新线程似乎有时间完成其工作: private void button1_Click(object sender, EventArgs e) {
ForEach
循环,但我在更新UI时遇到了问题(我有一个计数器显示类似于“x/y文件已处理”)。我制作了一个并行的Forloop
示例来说明我的问题(标签没有更新)
如果我更改button click事件方法,并添加一个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更新
我一直在寻找解决方案,但似乎什么都找不到(或者我可能在寻找错误的东西?)
问候
西蒙我猜数到2500万(并行!)不到一秒钟。。。因此,在计数完成之前,您的计时器不会被触发。如果添加
线程.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);
平行试验
//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);
当结果出现在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控件更新逻辑重新打包到主(调度)线程上。这只是一个简单的例子-只要确保你遵循了这个概念,你就会没事。我对此不太了解,但这可能与允许的线程数有关吗?如果平行的.For是把它们都拿起来,而不是留下任何东西让事件去抓,那么这可以解释这种行为。但我对此并不十分确定,所以这只是一个大胆的猜测,希望能激励那些了解更多的人。:)设置标签文本后,尝试放置一个Application.DoEvents()。这将让其他线程(特别是UI线程)有机会重新绘制屏幕您好,谢谢您的回复,我实际上想过对事件进行此操作,但无论如何,我需要时钟更新(任务消耗的时间)的计时器,所以我只是将其放在那里。不应该有什么区别,但这也可用:
联锁。增量(参考m_计数)
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 { }
}