2016-09-22 68 views
2

我能够从代码1更新UI而不是从2Task.Parallel更新UI。为什么这样?

代码1

Parallel.ForEach(names, name => 
    { 
    lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId; 
    }); 

代码2

Task.Factory.StartNew(() => 
      { 
       Parallel.ForEach(names, name => 
       { 
        lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId; 
       }); 
      }); 

我知道代码2不会更新用户界面,因为它的辅助线程。但为什么代码1更新UI?不平行foreach运行不同的线程?如果是的话,为什么它更新UI?

的代码1输出

enter image description here

+1

Parallel.ForEach重用父线程,这就是它阻塞的原因。其中一个线程实际上是UI线程,所以在更新UI –

+0

时没有问题,但如果它使用父线程,那么为什么有两个不同的线程ID? –

+1

此代码在哪里运行? –

回答

7

两个代码段工作...真的。

的问题是,第一码块的运行,而它的块UI线程。第二个代码块启动任务,然后继续。

的问题是无法在使用多线程的,由于这两个实施例使用多个线程来改变标签的值。问题在于表单状态。

我假设你运行表单构造的代码。在第一种情况下,没有创建句柄,所以的操作不需要UI线程。它只是更新后备值。在第二种情况下,在分割毫秒中需要创建任务,它会为表单创建句柄。当需要更新标签时,该操作需要UI线程

如果你把在该任务的Wait你会看到它也可以工作。如果您将代码移动到OnHandleCreated,则两个语句都会失败。

+2

在第二个线程尝试更新UI时,在“Click”事件中运行代码会导致跨线程异常。你是怎么尝试的?你在哪里运行代码?它可能在构造函数或'Load'处理程序中吗? –

+1

我从构造函数中运行它。如果你把代码移到'OnHandleCreated',它会失败。 –

+1

现在这是真正的答案。 – Evk

-1

Parallel.Foreach最终可能运行在不同的线程的任务。这里有一篇关于并行循环如何工作的文章MSDN

根据你的代码的位置,你仍然可以通过调用Invoke方法并传递一个委托来使用Task.Factory.StartNew方法。这将搜索控件的父链并找到具有窗口句柄的控件。

你里面的任务代码应该是这样的:

this.Invoke(new Action(() => this.lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId)); 
+0

这与这个问题有什么关系? OP并没有问为什么Parallel并行实际工作。问题是为什么禁止UI更新成功 –

+0

我认为问题是“不要求并行foreach运行不同的线程?如果是,那么它为什么更新UI?”......当MSDN文章解释如何和为什么并行foreach不总是运行在不同的线程中。 –

+0

问题不在于询问'Parallel.ForEach'。它询问意外的UI行为 –

2

此代码只能在窗体的构造函数中使用。此时该对象没有UI或UI句柄,因此不需要更新UI。代码只是改变标签控件的属性。

如果试图在OnLoad方法或Click事件处理程序,你会得到预期的跨线程访问异常相同的代码。

的形式和他们的控制都不是真正的UI对象。应用程序发送消息到操作系统,告诉哪些Windows控件显示在哪里,如何修改它们等。这些消息由Windows句柄标识。

构造函数虽然在表单甚至被创建之前执行,所以没有UI发送任何消息。更新标签文本时,只需修改包含在UI初始化后将发送给操作系统的值的字符串即可。

创建句柄后,对Text属性的任何修改都会向OS发送消息,而.NET明确阻止该消息。原因是从多个线程发送消息会导致无序传递到操作系统和一个乱七八糟的用户界面。

+1

感谢您用简单的语言解释帕特里克的答案。 –