How to update UI from child tasks in WinForms
Solution 1
Yes, you can explicitly call BeginInvoke
on the Window/Control that you want to communicate with. In your case this would look like this:
this.textBox.BeginInvoke(new Action(() =>
{
this.textBox.Text = "From child task.";
}));
Solution 2
You're passing the TaskScheduler
as state
(or antecedent
, as you called it). That doesn't make much sense.
I'm not sure what exactly do you want to do, but you need to specify the TaskScheduler
when you're starting the Task
, not when you're creating it. Also, it seems that childTask
doesn't have to be a field:
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task childTask = null;
Task.Factory.StartNew(
() =>
{
Thread.Sleep(1000);
childTask.Start(scheduler);
Thread.Sleep(1000);
});
childTask = new Task(
() =>
{
Thread.Sleep(2000);
textBox1.Text = "From child task";
});
Of course, all this is going to be much easier with C# 5 and await
:
public Form1()
{
InitializeComponent();
StartAsync();
}
private async void StartAsync()
{
// do some work
await Task.Run(() => { Thread.Sleep(1000); });
// start more work
var moreWork = Task.Run(() => { Thread.Sleep(1000); });
// update the UI, based on data from “some work”
textBox1.Text = "From async method";
// wait until “more work” finishes
await moreWork;
}
You can't make async
constructor, but you can run an async
method from it. “Doing work” doesn't run on the UI thread, because it was started explicitly through Task.Run()
. But since StartAsync()
was called directly, it executes on the UI thread.
Comments
-
devlife over 1 year
I've got a simple little winforms app that performs a long running process on another thread via a TPL Task. During this long running process I'd like to update the UI (the progress bar or something). Is there a way to do this without being required to .ContinueWith()?
public partial class Form1 : Form { private Task _childTask; public Form1() { InitializeComponent(); Task.Factory.StartNew(() => { // Do some work Thread.Sleep(1000); // Update the UI _childTask.Start(); // Do more work Thread.Sleep(1000); }); _childTask = new Task((antecedent) => { Thread.Sleep(2000); textBox1.Text = "From child task"; }, TaskScheduler.FromCurrentSynchronizationContext()); } }
Executing this code I get the ubiquitous exception:
Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.
-
jwillmer over 11 yearsI think you have to paste the textbox reference to the thread.
-
-
devlife over 11 yearsThat definitely looks to be on the way but I get a compilation error: Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type
-
devlife over 11 yearsThat's just the point. I don't want to wait until one task is completed to execute the other.
-
devlife over 11 yearsI created an Action and passed in the action instead of the anonymous method. Not sure why the above doesn't work but it got me 99% of the way there. Thanks Drew.
-
devlife over 11 yearsI updated my example to better show what I was trying to get across.
-
svick over 11 yearsIn that case, my first sample will still work for you. I also updated the C# 5 version.
-
Drew Marsh over 11 years@devlife Oops, that's my fault for being so used to just working with newer APIs. Yes, you need to explicitly wrap with a new Action(...) because of the way the signature of the old WinForms BeginInvoke API was designed. I will update my answer.
-
devlife over 11 yearsThat's exactly what I ended up doing. Thanks.
-
devlife over 11 yearsThat is pretty much what I was trying to do (just didn't get it quite right). Can't wait to start using C#5....