6

The following Code does not change the Text and stops executing the Task

private void button1_Click(object sender, EventArgs e)
    {
        label1.Text = "Test";
        Task.Run(() => MyAsyncMethod());
    }

    public async Task MyAsyncMethod()
    {
        label1.Text = "";
        //everything from here on will not be executed
    }

would be really handy if you could use async together with the UI

Servy
  • 202,030
  • 26
  • 332
  • 449
save_jeff
  • 403
  • 1
  • 5
  • 11

5 Answers5

13

would be really handy if you could use async together with the UI

The design of async was carefully done so you can use it naturally with the UI.

in my code i run a function that does a lot of IO and stuff that takes a long time

If you have asynchronous I/O methods (which you should), then you can just do this:

private async void button1_Click(object sender, EventArgs e)
{
  label1.Text = "Test";
  await MyMethodAsync();
}

public async Task MyMethodAsync()
{
  label1.Text = "";
  await ...; // "lot of IO and stuff"
  label1.Text = "Done";
}

That's the most natural approach.

However, if you need to run code on a background thread (e.g., it's actually CPU-bound, or if you just don't want to make your I/O operations asynchronous like they should be), then you can use IProgress<T>:

private void button1_Click(object sender, EventArgs e)
{
  label1.Text = "Test";
  var progress = new Progress<string>(update => { label1.Text = update; });
  await Task.Run(() => MyMethod(progress));
}

public void MyMethod(IProgress<string> progress)
{
  if (progress != null)
    progress.Report("");
  ...; // "lot of IO and stuff"
  if (progress != null)
    progress.Report("Done");
}

Under no circumstances should modern code use Control.Invoke or (even worse) Control.InvokeRequired.

Jake Smith
  • 2,332
  • 1
  • 30
  • 68
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    It seems that first exemple does an error : `System.InvalidOperationException: 'Cross-thread operation not valid: Control 'labelProject' accessed from a thread other than the thread it was created on.' ` – Matthieu Charbonnier Nov 24 '17 at 16:47
  • @MatthieuCharbonnier: If the button click handler is called in a properly-established UI context, then that cannot happen. I recommend that you ask your own question with a minimal, reproducible example. – Stephen Cleary Nov 25 '17 at 00:03
  • The button is called in a properly-established UI context, and it is happening. It happens when using `ConfigureAwait` with the `await` call. The same case is described here https://stackoverflow.com/questions/37898516/task-configureawait-behavior-after-ui-cross-thread-operation-exception – Matthieu Charbonnier Nov 25 '17 at 00:57
  • @MatthieuCharbonnier: `ConfigureAwait(false)` means you don't need the UI context. Since you *do* need the UI context, don't use `ConfigureAwait(false)`. – Stephen Cleary Nov 27 '17 at 14:25
  • Brilliant answer!! – PeterJ Mar 10 '18 at 12:53
  • Could you explain why invoke is evil? – Jonathan Nov 26 '19 at 19:28
  • 1
    @Jonathan: `Control.Invoke` is a WinForms-specific way for background work to send work to the UI thread. There's several things wrong with this; here's a few off the top of my head: 1) Background work should usually not care what thread it's on to begin with. 2) It makes testing much more difficult. 3) Background work sending work to the UI thread is backwards; code is invariably cleaner when the UI thread is the "controller". 4) This method is tied to a specific UI framework. – Stephen Cleary Nov 27 '19 at 02:49
  • I am not able to use label1.Text inside the async method. The intellisence also doesn't find it. I can only use it in the button click event. Why could that be? – variable Jul 29 '21 at 14:57
  • 1
    @variable: `label1` is a class instance variable. Your async method would have to be a non-static method on the same class to access it. – Stephen Cleary Jul 29 '21 at 18:26
12

for accessing a GUI control through a second thread you need to invoke. following example shows how to set a label's text properly

  private void setLabel1TextSafe(string txt)
  { 
       if(label1.InvokeRequired)
           label1.Invoke(new Action(() => label1.Text = txt));
       else
           label1.Text = txt;
  }

I hope this solves your problem

leAthlon
  • 734
  • 1
  • 10
  • 22
0

Task.Run is used to envelope an Action (that is not async) into a Task. Any Task that you want to execute should be awaited. Thus, that Task.Run of yours is rigorously doing nothing.

Mark that button1_Click event handler of yours as async. Then remove that Task.Run and instead do await MyAsyncMethod().

Ataur Rahman Munna
  • 3,887
  • 1
  • 23
  • 34
Gabriel Rainha
  • 1,713
  • 1
  • 21
  • 34
  • this is just e simplefied version of what it was doing. in my code i run a function that does a lot of IO and stuff that takes a long time but had no async funktions in it -> it did not ran async – save_jeff May 16 '15 at 22:48
  • So your function that did all that sync stuff should not be async. You'd then have await Task.Run(MyMethod). That way you'd run it asynchronously. – Gabriel Rainha May 17 '15 at 01:16
  • Also, as the visual thread would be the one awaiting the call, you wouldn't need to Marshall stuff back to UI thread. I'll add some code sample soon to show you what I mean. – Gabriel Rainha May 17 '15 at 01:18
-3

Try this. You don't need to fire a new thread to invoke the async method. compiler will do it for you.

private void button1_Click(object sender, EventArgs e)
    {
        label1.Text = "Test";
        MyAsyncMethod();
    }
public async Task MyAsyncMethod()
{
    return await Task.Run(() =>{
    label1.Text = "";
    //everything from here on will not be executed
     }
}
iaq
  • 173
  • 1
  • 2
  • 10
-3

I think both the questions and some of the answers are not clear. Depending on where in the task thread you need to update the label you have to use invoke. Otherwise you can leverage await and leverage the standard semantics.

    private async void button1_Click(object sender, EventArgs e)
    {
        label1.Text = "Starting to run a long task... carry on...";
        await snooze(3);
        label1.Text = "Done with long task";
    }

    public Task<int> snooze(int seconds)
    {
        label1.Text = "zzzz...";
        return Task.Run(
        () => {
            label1.Invoke(new Action(() => label1.Text = "For some reason I need to alert you here.. bad dream perhaps...direct access to label1 will fail"));
            Thread.Sleep(seconds * 1000);
            return  seconds;
           });

    }
Bob Sheehan
  • 160
  • 1
  • 6