Reputation: 7069
I am able to update UI from code 1 but not from 2.
Code 1
Parallel.ForEach(names, name =>
{
lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId;
});
Code 2
Task.Factory.StartNew(() =>
{
Parallel.ForEach(names, name =>
{
lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId;
});
});
I know that code 2 won't update UI because its a secondary thread. But why code 1 is updating UI? Don't Parallel foreach run different threads? if yes then why it is updating UI?
output of code 1
Upvotes: 2
Views: 618
Reputation: 131661
This code can only work in the constructor of the form. At this point the object has no UI or UI handle, so there is no UI to update. The code simply changes the property of the label control.
If you try the same code in the OnLoad
method or in a Click event handler you'll get the expected cross-thread access exception.
The forms and controls on them aren't the real UI objects. Applications send messages to the operating system telling which Windows controls to display where, how to modify them etc. The messages are identified by a Windows Handle.
The constructor though executes before the form is even created, so there is no UI to send any messages to. When you update the label text, you just modify the string that contains the value that will be send to the OS once the UI is initialized.
Once the handle is created, any modification of the Text
property sends a message to the OS, which is explicitly prevented by .NET. The reason is that sending messages from multiple threads will result in out-of-order delivery to the OS and a messed-up UI.
Upvotes: 2
Reputation: 157098
Both code segments work... Really.
The problem is that the first code block runs while it blocks the UI thread. The second code block starts the task and then continues.
The problem isn't in the use of multiple threads, since both examples use multiple threads to change the value of the label. The problem is in the state of the form.
I assume you run the code in the form constructor. In the first case, there is no handle created, so the operation doesn't need the UI thread. It just updates the backing value. In the second case, in the split millisecond it needs to create the task, it creates the handle for the form. When it needs to update the label the operation needs the UI thread.
If you put in a Wait
on the task you will see it will also work. If you move the code to the OnHandleCreated
, it both statements will fail.
Upvotes: 7
Reputation: 88
The Parallel.Foreach
could eventually run the tasks in different threads. Here's an article about how Parallel loops work on MSDN
Depending on where your code is, you could still use the Task.Factory.StartNew
method by calling the Invoke method and passing a delegate. This will search up the control's parent chain and find a control that has a window handle.
Your code inside the task would look like this:
this.Invoke(new Action(() => this.lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId));
Upvotes: -1