How To Async Await

Published on Tuesday, January 02, 2024

How To: Async / Await

The purpose of async code is to allow other code to run while the async code is running. See Asynchronous programming with async and await for much more detailed documentation.

A key point to understanding async code is to realize that when execution hits an "await" keyword, execution exits out of the current method and goes about its business until that call returns, at which point code execution jumps back to the next line.

s_downloadButton.Clicked += async (o, e) =>
{
    var stringData = await s_httpClient.GetStringAsync(URL); // <- after this line, execution jumps out of method
    DoSomethingWithData(stringData);                         // <- When GetStringAsync completes, execution jumps back
};

Generally, if a method doesn't have an await in it, it shouldn’t be declared async.

Don’t: 🙁

public async Task UnneccesaryAsync()
{
  SomeVoidMethod();
  SomeOtherVoidMethod();
}

Conversely, if a method does have an await in it, it should be async.

Don’t: 🙁

public void MissingAsync()
{
  await SomeTask(); // Won't compile
  SomeOtherVoidMethod();
}

–or–

public void MissingAsync()
{
  SomeTask(); // Should await
  SomeOtherVoidMethod();
}

The StateHasChanged method lets Blazor know that it should re-render the component. It’s not necessary in an event handler (like “OnClick”) or in a lifecycle method; Blazor already calls StateHasChanged internally in those cases. But if another method changes the state of the component, you need to call StateHasChanged.

private void IncrementCounterCalledFromInternalMethod()
{
  _counter++;
  StateHasChanged();
}

When that other method is called from another context, like from a service update callback or a timer, StateHasChanged should be called from InvokeAsync. The purpose of InvokeAsync is to make calls on the same context as the component. InvokeAsync returns a Task, but it doesn't have to be awaited.

Since the lambda handed to the InvokeAsync runs on the UI thread, keep it short and avoid other await calls inside it; it blocks the thread.

public async Task IncrementCounterCalledFromTimerOrServiceUpdate()
{
  counter = await SomeDataService(); // <- Do this here, outside of the InvokeAsync
  InvokeAsync(() =>
  {
    // counter = await SomeDataService(); // <- Don't do this!
    counter++;
    StateHasChanged();
  });
}

See ASP.NET Core Blazor synchronization context and Blazor University - Thread safety using InvokeAsync for a more detailed explanation.

If you are having problems with async / await code (and boy, I sure have), try to do things the “async” way. Don’t try to store tasks for later, or call async code from non-async code, or vice-versa. Don’t try to coerce tasks to complete, or use .Result. I’m not saying you can’t make it work, but there’s probably an easier way.