Avrohom Yisroel
Avrohom Yisroel

Reputation: 9440

Why isn't my async code running async?

WARNING I'm a complete newbie with async/await, and so am probably misunderstanding this completely!

I'm trying to work out how this stuff works, and tried a simple bit of code in the view of a WPF window. I added a button click event handler, and added some sync and async methods as follows...

public partial class MainWindow {
  private Random _r = new Random(DateTime.Now.Millisecond);

  public MainWindow() {
    InitializeComponent();
  }

  private async void Bleah_Click(object sender, RoutedEventArgs e) {
    LstMessages.Items.Clear();
    AddToMsg("Starting...");
    DoSyncStuff();
    await DoStuffAsync();
    DoMoreStuffSync();
    AddToMsg("Done");
  }

  private void DoSyncStuff() {
    int delay = _r.Next(500, 1500);
    AddToMsg("DoSyncStuff - waiting for " + delay + "ms");
    Thread.Sleep(delay);
    AddToMsg("DoSyncStuff - finished");
  }

  private void DoMoreStuffSync() {
    int delay = _r.Next(500, 1500);
    AddToMsg("DoMoreStuffSync - waiting for " + delay + "ms");
    Thread.Sleep(delay);
    AddToMsg("DoMoreStuffSync - finished");
  }

  private async Task DoStuffAsync() {
    await Task.Run(() => {
      int delay = _r.Next(500, 1500);
      AddToMsg("DoStuffAsync - waiting for " + delay + "ms");
      Thread.Sleep(delay);
      AddToMsg("DoStuffAsync - finished");
    });
  }

  private void AddToMsg(string msg) {
    Dispatcher.BeginInvoke(
      new Action(() => { LstMessages.Items.Add(DateTime.Now.ToString("HH:mm:ss.fff") + " - " + msg); }));
  }

LstMessages is a ListBox on the window.

When I click the button, I see that the three methods are always executed in the order I call them, irrespective of the length of each delay.

I'm obviously misunderstanding how this stuff works, but after reading around for a few hours, and trying lots of variations of the code, I can't get it to work how I expect.

Please can anyone clarify what I've done wrong here?

Upvotes: 0

Views: 169

Answers (4)

Will Ray
Will Ray

Reputation: 10879

All you have to do is drop the await keyword in your code.

To quote a blog post by Eric Lippert:

Whenever a task is “awaited”, the remainder of the current method is signed up as a continuation of the task, and then control immediately returns to the caller. When the task completes, the continuation is invoked and the method starts up where it was before.

By adding in the await keyword, you're effectively saying "once this async method has completed, carry on with the rest of this method".

It might be easier to understand this with methods that return a value. The following program will start off two methods right away, and will await the result of the async method after it calls the sync method. You can try moving the await line around to watch the difference in behavior.

class Program
{
    static void Main(string[] args)
    {
        MainAsync();
        Console.ReadKey();
    }

    static async void MainAsync()
    {
        var task = GetNumberAsync();
        var syncNumber = GetNumber();
        var asyncNumber = await task; // moving this line above "GetNumber();" will make these run in order

        Console.WriteLine(syncNumber);
        Console.WriteLine(asyncNumber);
    }

    private static int GetNumber()
    {
        Console.WriteLine("DoSomeWork - started");
        Thread.Sleep(1000);
        Console.WriteLine("DoSomeWork - finished");
        return 11;
    }

    private static async Task<int> GetNumberAsync()
    {
        Console.WriteLine("GetNumberAsync - started");
        await Task.Delay(1000);
        Console.WriteLine("GetNumberAsync - finished");
        return 22;

    }
}

Upvotes: 2

Chris
Chris

Reputation: 915

Try this approach, it appears you were kicking off an async method but immediately waiting for it in the UI thread.

private async void Bleah_Click(object sender, RoutedEventArgs e) 
{
    LstMessages.Items.Clear();
    AddToMsg("Starting...");
    DoSyncStuff();
    Task t = DoStuffAsync();
    DoMoreStuffSync();
    await t;
    AddToMsg("Done");
}

Upvotes: 1

Lincoln Green
Lincoln Green

Reputation: 215

The important thing to understand is that async and await keywords don't cause additional threads to be created. (Task.Run() CAN move work to another thread). So what's really going on in your code?

So, in your code, the first call to DoSyncStuff() pauses the main thread. Your call to DoStuffAsync() will not even be executed until after DoSyncStuff() fully completes.

Your call to DoStuffAsync is triggered as though it's async - but because you used the await keyword in the caller function 'await DoStuffAsync()', main thread control will return to the Bleah_Click() caller (which for your purposes won't do anything super interesting). Once DoStuffAsync() completes, control returns to Bleah_Click, and DoMoreStuffSync() is executed - which again pauses your main thread.

AS to your question: I can't tell you what you've "done wrong" as you haven't really specified your desired result - if you want to pause your UI thread execution and execute all your functions in the listed order, then you've done everything right.

Upvotes: 1

thunder2709
thunder2709

Reputation: 51

What you are seeing makes sense, as you are performing all actions on the main UI thread. You need to either create and manage your own Thread/BackgroundWorker object, or submit a method to the ThreadPool

BackgroundWorker

Thread

ThreadPool

Each approach has its own pros and cons, which can be found in other answers here. Give these links a read and try out the examples

Upvotes: 0

Related Questions