What is the main difference between returning a task by await keyword and returning without await keyword?

I'm wrapping AspNet.Identity. But something confuses me about TPL.

First example:

    public virtual async Task<IdentityResult> RemovePasswordAsync(string userId)
    {
        var user = _store.FindByIdAsync(userId).Result;
        if (user == null)
            throw new InstanceNotFoundException("user");

        user.PasswordHash = String.Empty;
        user.SecurityStamp = String.Empty;
        return await UpdateAsync(user);
    }

    public virtual async Task<IdentityResult> UpdateAsync(TUser user)
    {
        await _store.UpdateAsync(user);
        return new IdentityResult();
    }

Second example:

    public virtual Task<IdentityResult> RemovePasswordAsync(string userId)
    {
        var user = _store.FindByIdAsync(userId).Result;
        if (user == null)
            throw new InstanceNotFoundException("user");

        user.PasswordHash = String.Empty;
        user.SecurityStamp = String.Empty;
        return UpdateAsync(user);
    }

    public virtual async Task<IdentityResult> UpdateAsync(TUser user)
    {
        await _store.UpdateAsync(user);
        return new IdentityResult();
    }

And client will call this like:

    result = await _userManager.RemovePasswordAsync(user.Id);

My first question is:

When the client calls the second method, the work is offloaded to a threadpool thread from the IO thread. And when RemovePasswordAsync is called it calls UpdateAsync which has an await keyword. So, at that point does this threadpool thread offload to another threadpool thread? Or does TPL continue using the same thread instead?

And my second question is; what is the main difference between the first implementation and the second implementation of constructing this async method?

EDIT:

This is the update method of the UserStore class. (_store.UpdateAsync(user))

    public Task UpdateAsync(TUser user)
    {
        if (user == null)
            throw new ArgumentNullException("user");

        return _userService.UpdateAsync(user);
    }

And this is the update method of the UserService class

    public Task UpdateAsync(TUser user)
    {
        return Task.Factory.StartNew(() => Update(user));
    }

Upvotes: 8

Views: 1836

Answers (2)

dcastro
dcastro

Reputation: 68670

I'll answer your first question.

You're misunderstanding how async/await works.

An async method will run synchronously at least until it hits the first await statement. When it hits an await, it has two options:

  • If the awaitable (e.g. Task) has already completed, execution carries on the current context (i.e. UI Thread, or ASP.NET request's context).
  • If the awaitable hasn't completed yet, it wraps the rest of the method's body and schedules that to be executed on the current context (i.e. UI Thread) (*) when the task completes.

By this definition, your whole code will run on the same ASP.NET request's context.

_store.UpdateAsync may however spawn a ThreadPool thread (e.g., by using Task.Run).

Updated

According to your updated answer, Update(user) will run on a ThreadPool thread. Everything else will run on the current context.


(*) The rest of the method's body will be scheduled to run on a ThreadPool thread if there's no synchronization context (i.e., Console Application).

Upvotes: 3

noseratio
noseratio

Reputation: 61686

And my second question is; what is the main difference between the first implementation and the second implementation of constructing this async method?

Your first implementation can and should be improved by replacing the blocking _store.FindByIdAsync(userId).Result with asynchronous await _store.FindByIdAsync(userId):

public virtual async Task<IdentityResult> RemovePasswordAsync(string userId)
{
    var user = await _store.FindByIdAsync(userId);
    if (user == null)
        throw new InstanceNotFoundException("user");

    user.PasswordHash = String.Empty;
    user.SecurityStamp = String.Empty;
    return await UpdateAsync(user);
}

Without such update, the difference is perhaps described best by Eric Lippert here. One particular thing is how exceptions can possibly be thrown and handled.

Updated to address the comments. You should not be offloading with Task.Factory.StartNew or Task.Run in ASP.NET. This is not a UI app where you need to keep the UI responsive. All this does is just adding an overhead of a thread switch. The HTTP request handler which calls Task.Run then awaits or blocks will take at least the same number of threads to complete, you will not improve scalability and only hurt the performance. On the other hand, it does make sense to use naturally IO-bound tasks, which don't use a thread while pending.

Upvotes: 2

Related Questions