3

Just created a WPF project .net 4.6

And have put this code inside

lbl1 is a label on the GUI

But the label is never updated or the while loop continue only 1 time

        private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        var t = Task.Run(
       async () =>
      {
           await AsyncLoop();
       });

    }

    async Task AsyncLoop()
    {
        while (true)
        {
            string result = await LoadNextItem();
            lbl1.Content = result;
        }
    }

    private static int ir11 = 0;
    async Task<string> LoadNextItem()
    {
        ir11++;
        return "aa " + ir11;
    }
Furkan Gözükara
  • 22,964
  • 77
  • 205
  • 342
  • 2
    Possible duplicate of [How to update the GUI from another thread in C#?](http://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c); see [this answer](http://stackoverflow.com/a/18033198/3283203) specifically. – Kilazur Jul 28 '16 at 14:13
  • I don't understand WHY 'lbl1.Content = result' does not update the label. What did Microsoft designers think the programmer wants the computer to do with that line of code? It is horrendous to have to jump through all these hoops to get a simple control to update! – Paul McCarthy Jun 30 '22 at 21:23

3 Answers3

2

The C# compiler is giving you a warning that tells you what the problem is.

Specifically, this method is not asynchronous:

async Task<string> LoadNextItem()
{
  ir11++;
  return "aa " + ir11;
}

The compiler message will inform you that this async method has no await statements, and thus will run synchronously. You should only use async where it makes sense, usually for I/O-based operations, e.g.:

async Task<string> LoadNextItemAsync()
{
  await Task.Delay(100); // Placeholder for actual asynchronous work.
  ir11++;
  return "aa " + ir11;
}

Alternatively, if you don't have asynchronous operations, but rather you have some tight CPU-bound loops, then you can push those off to a background thread via Task.Run:

string LoadNextItem()
{
  ir11++;
  return "aa " + ir11;
}

while (true)
{
  string result = await Task.Run(() => LoadNextItem());
  lbl1.Content = result;
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
2

By invoking Task.Run you broke your association(SynchronizationContext) with the GUI thread (or WPF Dispatcher) and lost most of the async/await 'goodness'.

Why not use an async void event handler and just come back to the SynchronizationContext(GUI Thread/Dispatcher) for each step?

private async void Button_Click_1(object sender, RoutedEventArgs e)
{
    while (true)
    {
        string result = await LoadNextItem();
        lbl1.Content = result;
    }
}

private static int ir11 = 0;
Task<string> LoadNextItem()
{
    await Task.Delay(1000); // placeholder for actual async work
    ir11++;
    return "aa " + ir11;
}

Or if you really want to separate the state machine for the 'on-going' operations, try passing an IProgress<T> (the default impl. Progress<T> or specifically Progress<string> should work great in this case). See this article by @Stephen Cleary

His example is very close to what you stated in the question. I've copied it here for SO independence.

public async void StartProcessingButton_Click(object sender, EventArgs e)
{
  // The Progress<T> constructor captures our UI context,
  //  so the lambda will be run on the UI thread.
  var progress = new Progress<int>(percent =>
  {
    textBox1.Text = percent + "%";
  });

  // DoProcessing is run on the thread pool.
  await Task.Run(() => DoProcessing(progress));
  textBox1.Text = "Done!";
}

public void DoProcessing(IProgress<int> progress)
{
  for (int i = 0; i != 100; ++i)
  {
    Thread.Sleep(100); // CPU-bound work
    if (progress != null)
      progress.Report(i);
  }
}

Edit: I must admit, while Progress<T> is a nice abstraction, in this case it is just going to fall down to Dispatcher.Invoke as @Pamparanpa suggested.

SensorSmith
  • 1,129
  • 1
  • 12
  • 25
1

Please user Application.Current.Dispatcher.Invoke to access the ui thread

async Task AsyncLoop()
    {
        while (true)
        {
            string result = await LoadNextItem();
Application.Current.Dispatcher.Invoke(new Action(() => {  lbl1.Content = result; }));

        }
    }
suulisin
  • 1,414
  • 1
  • 10
  • 17
  • i see. so it was actually throwing a silent error thus stopping. any way to type this quickly or copy paste? Application.Current.Dispatcher.Invoke(new Action(() => { – Furkan Gözükara Jul 28 '16 at 14:18
  • Yes you can access ui thread from another thread directly in wpf. – suulisin Jul 28 '16 at 14:19
  • does this wait until ui is updated? or just dispatcher invoke and return immediately? – Furkan Gözükara Jul 28 '16 at 14:23
  • it is a non blocking operation – suulisin Jul 28 '16 at 14:23
  • This works, but misses out on the inherent support for async/await to 'come back to' the ui thread following an await. The only advantage to awaiting in the above function is that you won't tie up a thread pool thread while 'waiting' on LoadNextItem. That's considerable, but not using it 'fully' in this case. – SensorSmith Jul 29 '16 at 17:21
  • @SensorSmith `The only advantage to awaiting in the above function is that you won't tie up a thread pool thread ` You're not even doing that, as `LoadNextItem` runs synchronously. – Servy Jul 29 '16 at 17:29
  • @Servy You're right as the question is written. I am assuming that was an 'oversimplification bug' by the OP in preparing the question. – SensorSmith Jul 29 '16 at 17:32
  • Is there a MVVM equivalent of Application.Current.Dispatcher.Invoke – Paul McCarthy Jun 28 '22 at 16:22