c#并行。用于和UI更新?

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

我试图实现一个Parallel.ForEach循环来替换旧的
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 { }
}