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;
    }
MonsterMMORPG
  • 21,378
  • 72
  • 196
  • 320
  • 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

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
  • 406,130
  • 70
  • 637
  • 767
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,041
  • 11
  • 22
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,405
  • 1
  • 10
  • 16
  • 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(() => { – MonsterMMORPG 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? – MonsterMMORPG 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