通过await关键字返回任务与不使用await关键字返回之间的主要区别是什么?

时间:2014-02-25 13:06:23

标签: c# asp.net multithreading task-parallel-library

我正在包装AspNet.Identity。但有些事让我对TPL感到困惑。

第一个例子:

    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();
    }

第二个例子:

    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();
    }

客户会称之为:

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

我的第一个问题是:

当客户端调用第二个方法时,工作将从IO线程卸载到线程池线程。当RemovePasswordAsync被调用时,它会调用UpdateAsync,它有一个await关键字。那么,在这一点上,这个线程池线程是否会卸载到另一个线程池线程?或者TPL是否继续使用相同的线程?

我的第二个问题是;构建此async方法的第一个实现和第二个实现之间的主要区别是什么?

修改

这是UserStore类的更新方法。 (_store.UpdateAsync(user)

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

        return _userService.UpdateAsync(user);
    }

这是UserService

的更新方法
    public Task UpdateAsync(TUser user)
    {
        return Task.Factory.StartNew(() => Update(user));
    }

2 个答案:

答案 0 :(得分:3)

我会回答你的第一个问题。

你误解了async / await是如何工作的。

async方法将同步运行至少,直到它遇到第一个await语句。 当它到达await时,它有两个选项:

  • 如果等待(例如Task)已经完成,则执行将继续当前上下文(即UI线程或ASP.NET请求的上下文)。
  • 如果等待还没有完成,它会包装方法的其余部分,并在任务完成时安排在当前上下文(即UI线程)(*)上执行。

通过这个定义,您的整个代码将在相同的ASP.NET请求的上下文中运行。

然而,

_store.UpdateAsync可能会产生一个ThreadPool线程(例如,使用Task.Run)。

<强>更新

根据您更新的答案,Update(user)将在ThreadPool线程上运行。其他所有内容都将在当前环境中运行。


(*)方法主体的其余部分将被安排在ThreadPool线程上运行,如果没有同步上下文(即控制台应用程序)。

答案 1 :(得分:2)

  

我的第二个问题是;两者之间的主要区别是什么   第一次实施和第二次实施   这个异步方法?

通过使用异步_store.FindByIdAsync(userId).Result替换阻止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);
}

如果没有这样的更新,Eric Lippert here可能最好地描述了这种差异。一个特别的事情是how exceptions can possibly be thrown并处理。

已更新以解决评论。您应该在ASP.NET中使用Task.Factory.StartNewTask.Run进行卸载。这是您需要保持UI响应的UI应用。所有这一切只是增加了线程切换的开销。调用Task.Run然后等待或阻塞的HTTP请求处理程序将至少完成相同数量的线程,您不会提高可伸缩性并且只会损害性能。另一方面,使用自然的IO绑定任务是有意义的,这些任务在挂起时不使用线程。