Reputation: 115
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
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:
Task
) has already completed, execution carries on the current context (i.e. UI Thread, or ASP.NET request's context).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
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