Accessing UI controls in Task.Run with async/await on WinForms

Solution 1:

When you use Task.Run(), you're saing that you don't want the code to run on the current context, so that's exactly what happens.

But there is no need to use Task.Run() in your code. Correctly written async methods won't block the current thread, so you can use them from the UI thread directly. If you do that, await will make sure the method resumes back on the UI thread.

This means that if you write your code like this, it will work:

private async void button1_Click(object sender, EventArgs e)
{
    await Run();
}

private async Task Run()
{
    await File.AppendText("temp.dat").WriteAsync("a");
    label1.Text = "test";
}

Solution 2:

Try this:

replace

label1.Text = "test";

with

SetLabel1Text("test");

and add the following to your class:

private void SetLabel1Text(string text)
{
  if (InvokeRequired)
  {
    Invoke((Action<string>)SetLabel1Text, text);
    return;
  }
  label1.Text = text;
}

The InvokeRequired returns true if you are NOT on the UI thread. The Invoke() method takes the delegate and parameters, switches to the UI thread and then calls the method recursively. You return after the Invoke() call because the method has already been called recursively prior to the Invoke() returning. If you happen to be on the UI thread when the method is called, the InvokeRequired is false and the assignment is performed directly.

Solution 3:

Try this

private async Task Run()
{
    await Task.Run(async () => {
       await File.AppendText("temp.dat").WriteAsync("a");
       });
    label1.Text = "test";
}

Or

private async Task Run()
{
    await File.AppendText("temp.dat").WriteAsync("a");        
    label1.Text = "test";
}

Or

private async Task Run()
{
    var task = Task.Run(async () => {
       await File.AppendText("temp.dat").WriteAsync("a");
       });
    var continuation = task.ContinueWith(antecedent=> label1.Text = "test",TaskScheduler.FromCurrentSynchronizationContext());
    await task;//I think await here is redundant        
}

async/await doesn't guarantee that it will run in UI thread. await will capture the current SynchronizationContext and continues execution with the captured context once the task completed.

So in your case you have a nested await which is inside Task.Run hence second await will capture the context which is not going to be UiSynchronizationContext because it is being executed by WorkerThread from ThreadPool.

Does this answers your question?