异步代码在 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?
做"Task.FromResult"是否正确?我的意思是"Task.FromResult 实际上会变成同步的吗?应该是什么?
对上述内容进行编码的正确方法是什么? 配置Await(false)呢 异步应该是"一直向下,包括数据层以避免死锁"
任何示例代码/片段将不胜感激
非常感谢您的任何反馈
代码没有误导性。 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>
的 async
和 await
最终只会浪费编译器生成代码的 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;
}