Home > Blockchain >  Using await Task.Run(() => someMethodAsync()) vs await someMethodAsync() in a UI based app
Using await Task.Run(() => someMethodAsync()) vs await someMethodAsync() in a UI based app

Time:10-23

I read this article with this example:

class MyService
{
  /// <summary>
  /// This method is CPU-bound!
  /// </summary>
  public async Task<int> PredictStockMarketAsync()
  {
    // Do some I/O first.
    await Task.Delay(1000);

    // Tons of work to do in here!
    for (int i = 0; i != 10000000;   i)
      ;

    // Possibly some more I/O here.
    await Task.Delay(1000);

    // More work.
    for (int i = 0; i != 10000000;   i)
      ;

    return 42;
  }
}

It then describes how to call it depending on if you're using a UI based app or an ASP.NET app:

//UI based app:
private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => myService.PredictStockMarketAsync());
}

//ASP.NET:
public class StockMarketController: Controller
{
  public async Task<ActionResult> IndexAsync()
  {
    var result = await myService.PredictStockMarketAsync();
    return View(result);
  }
}

Why do you need to use Task.Run() to execute PredictStockMarketAsync() in the UI based app?

Wouldn't using await myService.PredictStockMarketAsync(); in a UI based app also not block the UI thread?

CodePudding user response:

By default, when you await an incomplete operation, the sync-context is captured (if present). Then, when reactivated by the completion of the async work, that captured sync-context is used to marshal the work. Many scenarios don't have a sync-context, but UI applications such as winforms and WPF do, and the sync-context represents the UI thread (basically: this is so that the default behaviour becomes "my code works" rather than "I get cross-thread exceptions when talking to the UI in my async method"). Essentially, this means that when you await Task.Delay(1000) from a UI thread, it pauses for 1000ms (releasing the UI thread) but then resumes on the UI thread. This means that you "Tons of work to do in here" happens on the UI thread, blocking it.

Usually, the fix for this is simple: add .ConfigureAwait(false), which disables sync-context capture:

await Task.Delay(1000).ConfigureAwait(false);

(usually: add this after all await in that method)

Note that you should only do this when you know that you don't need what the sync-context offers. In particular: that you aren't going to try to touch the UI afterwards. If you do need to talk to the UI, you'll have to use the UI dispatcher explicitly.

When you used Task.Run(...), you escaped the sync-context via a different route - i.e. by not starting on the UI thread (the sync-context is specified via the active thread).

  • Related