异步代码在 aspnet 标识中具有误导性

Async code misleading in aspnet identity

我需要为 aspnetidentity 实现自定义存储提供程序。 我环顾四周,发现相当多 few.However 它们对我来说似乎都是错误的。

我的理解是,如果你有一个以 "async" 结尾的方法,那么它应该是异步的。

查看从某人的代码中获取的示例,它散布在各处。

发现下面的内容非常具有误导性,因为从我所看到的来看它根本不是异步的:

    public Task<TUser> FindByIdAsync(int userId)
    {
        TUser result = userTable.GetUserById(userId) as TUser;  //this is not async 
        if (result != null)
        {
            return Task.FromResult<TUser>(result);
        }

        return Task.FromResult<TUser>(null);
    }

应该这样编码吗?:

     public async Task<TUser> FindByIdAsync(int userId)
        {

            TUser result = userTable.GetUserByIdAsync(userId) as TUser;
            if (result != null)
            {
                return await Task.FromResult<TUser>(result);
            }

            return await Task.FromResult<TUser>(null);
        }

    Questions?
  1. 这样做"Task.FromResult"是否正确?我的意思是“Task.FromResult 实际上变成了同步?它应该是什么?

  2. 编写上述代码的正确方法是什么? configureAwait(false) 怎么样 异步应该是 "All the way down including datalayer to avoid deadlocking"

任何示例代码/片段将不胜感激

非常感谢任何反馈

初始代码绝对不是异步的。它看起来像是为了应对 API 设计。

然而,提议的更改对我来说也不是异步的。 Task.FromResult 只是创建一个已完成的任务并给出结果,不会使任何异步操作或执行任何类型的可等待代码,因此您不应该等待它。

在您的情况下,假设 GetUserByIdAsync return 是 Task<TUser>,并假设此代码的全部目的(看起来)总是 return 完成任务(从未出错或取消),这可以重写为:

public async Task<TUser> FindByIdAsync(int userId)
{
   var tResult = userTable.GetUserByIdAsync(userId);
   TUser result = null;
   try
   {
     result = await tResult;
   } 
   except
   {
     // Bad idea, but here goes to your first snippet
   }
   return Task.FromResult<TUser>(result);
}

注意:正如@PanagiotisKanavos 评论的那样,这是个坏主意,它隐藏了一个可能的错误状态,你永远不会知道你的 null 结果是否出现未找到用户,或者出现错误情况:我会避免它。

如果 faulted/cancelled 状态有效,这可能只是:

public Task<TUser> FindByIdAsync(int userId)
{
   return userTable.GetUserByIdAsync(userId);
}

async 方法是编译器构建某些东西的一种方式,return 承诺 我们的 未来.对于 .NET Framework 和 C#,这是 Task.

await 指令接受任何 awaitableTask 恰好是一个。它不知道也不关心被调用的 method/operations 是否真的是异步的。

Task.FromResult<T> returns 一个已完成的任务,如果您在 async 方法内的 await 指令中使用它,它将被认为已同步完成并执行会继续。

因此,仅调用 Task.FromResult<T> 使用 asyncawait 最终只会浪费 CPU 编译器生成的代码的周期和内存在 运行 时间浪费了更多 CPU 周期和内存。

因为 async 方法总是 return 是 Task 一个决定让它隐含以提高可读性并给它某种 sync感觉。编译会将 return 值包装在 Task 中。这就是为什么您不能直接 return Task.FromResult<TUser>(result)Task.FromResult<TUser>(null) 并等待它获取值的原因。

因此,async 等同于您的同步代码将是:

 public async Task<TUser> FindByIdAsync(int userId)
 {
    var result = await userTable.GetUserByIdAsync(userId) as TUser;
    if (result != null)
    {
        return result;
    }

    return null;
}

或:

 public async Task<TUser> FindByIdAsync(int userId)
 {
    return await userTable.GetUserByIdAsync(userId) as TUser;
 }

代码没有误导性。 ASP.NET 身份框架旨在通过 returning Task 并通过向方法名称添加 Async 后缀来指示这一点来提供异步接口:

Task<TUser> FindByIdAsync(int userId)

但是,底层提供程序可能没有异步方法。在那种情况下,您无法创建异步实现,但您仍然必须实现接口,并且要做到这一点,将使用 Task.FromResult 与您在第一个代码片段中所做的完全相同。

使用同步代码实现异步方法

public Task<TUser> FindByIdAsync(int userId)
{
    TUser result = userTable.GetUserById(userId) as TUser;
    return Task.FromResult<TUser>(result);
}

如果您的底层提供程序支持异步方法,您应该使用 async 和 await。

使用异步代码实现异步方法

public async Task<TUser> FindByIdAsync(int userId)
{
    TUser result = (await userTable.GetUserByIdAsync(userId)) as TUser;
    return result;
}

请注意,Task.FromResult 未被使用。 Task.FromResult 仅当您有一个由同步代码创建的 TResult 并且必须将其转换为异步代码所需的 Task<TResult> 时才需要。

有时,您的底层提供者可以 return 所需的 Task<TUser> 而无需任何进一步的工作。在这种情况下,您可以删除 async 和 await 并仍然提供异步实现。这可能会导致代码效率稍高:

public Task<TUser> FindByIdAsync(int userId)
{
    Task<TUser> result = userTable.GetUserByIdAsync(userId);
    return result;
}