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.
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.";
}));
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.