crypted
crypted

Reputation: 10306

Async await with Task.FromResult(0)

Not sure if I am messed up with my understanding of how async await works, but here is the problem I am stucked at. Consider a contrived example

  1. This code blocks UI

    public async void LoginButtonClicked()
    {
     //create a continuation point so every following statement will get executed as ContinueWith
     await Task.FromResult(0);
     //this call takes time to execute
     Remote.Login("user","password");
    }
    
  2. But this does not (obviously)

     public void LoginButtonClicked()
     {
     Task.Run(()=>{ Remote.Login("user","password");});
      }
    

I like to use method 1 because I don't want to spin long work using a Task.Run rather I prefer framework handle this form me. But the problem is The call to Method 1 seems blocking.

Upvotes: 4

Views: 11237

Answers (3)

andreim
andreim

Reputation: 3503

Every async method has its context.

When the Task starts it might run in a new SynchronizationContext. "Might" because if the task is already completed, like Task.FromResult(0), then no other SynchronizationContext is created and the original one is used.

Awaiting for a task means that when the task is finished, the next statement will run in the original SynchronizationContext.

This behavior can be changed using the Task.ConfigureAwait(continueOnCapturedContext: false). This means that the next statement will continue on the same context. But this will change nothing by doing Task.FromResult(0).ConfigureAwait(false) because the task is already completed and the original context will be used.

Therefore your Remote.Login("user","password"); will be run in the original context, thus blocking the UI thread which runs on the same context.

If you would have something like:

public async void LoginButtonClicked()
{
  await Task.Delay(5000).ConfigureAwait(false);
  Remote.Login("user","password");
}

Then Remote.Login("user","password"); would execute in the thread pool context thus being on a different context than the original UI context.

So the best way to fix your code is to create a Remote.LoginAsync() as stated in @Nicholas W answer.

NOTE on performance: if you have an async method with multiple await statements, and you don't need some of those awaits to do work on UI or web app thread, then you can use Task.ConfigureAwait(false) in order to prevent multiple switches to the UI/web-app context which slices its execution time.

Upvotes: 2

Nai
Nai

Reputation: 67

  1. You run in parallel Task.FromResult(0); and still wait for Remote.Login("user","password"); to be finished
  2. You run Remote.Login("user","password"); asynchronously.

You have to create async version of Remote.Login

    async Task LoginAsync(string user, string password)
    {
        Remote.Login(user, password);
        await Task.FromResult(0);
    }

and call it

    public async void LoginButtonClicked()
    {
        await LoginAsync("user", "password");
    }

Upvotes: -2

Nicholas W
Nicholas W

Reputation: 2241

Using await/async only stops you from blocking the UI if all the long-running operations you call are async. In your example your Remote.Login is a synchronous call, so regardless of what the prior await line does, this will block your UI.

You need to either get an async version of your actual long-running operation (eg something returning a Task) or if that is not possible, then you can resort to Task.Run in order to move this work to the ThreadPool.

What you want if possible:

public async void LoginButtonClicked()
{
    await Remote.LoginAsync("user","password");
    // do anything else required
}

Upvotes: 5

Related Questions