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

本文关键字:误导性 标识 代码 aspnet 异步 | 更新日期: 2023-09-27 18:31:58

我需要为 aspnetidentity 实现一个自定义存储提供程序。我仔细环顾四周,发现了不少。然而,在我看来,它们似乎都是错误的。

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

请参阅从某人的代码中获取的示例,这分散在各个地方。

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

    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. 对上述内容进行编码的正确方法是什么? 配置Await(false)呢 异步应该是"一直向下,包括数据层以避免死锁"

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

非常感谢您的任何反馈

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

代码没有误导性。 ASP.NET 标识框架旨在通过返回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。 仅当同步代码创建的TResult并且必须将其转换为异步代码所需的Task<TResult>时,才需要Task.FromResult

有时,基础提供程序可以返回所需的Task<TUser>而无需任何进一步的工作。在这种情况下,您可以删除异步和等待,但仍提供异步实现。这可能会导致代码效率稍微提高一点:

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

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

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

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

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结果是找不到用户,还是有错误条件:我会避免它。

如果错误/取消状态有效,则可能只是:

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

async方法是编译器构建一些东西的方法,这些东西将返回我们的未来在.NET Framework和C#的情况下,这是一个Task

await指令需要任何等待Task恰好是一个。它不知道也不关心被调用的方法/操作是否真的是异步的。

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

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

由于 async 方法始终返回Task因此决定将其隐式,以提高可读性并赋予其某种同步感。编译会将返回值包装在Task中。这就是为什么您无法直接返回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;
 }