为什么 C# 允许进行覆盖异步

本文关键字:覆盖 异步 许进行 为什么 | 更新日期: 2023-09-27 18:37:28

在 C# 中,重写方法时,允许在原始方法不同步时使重写异步。这似乎是糟糕的形式。

让我感到疑惑的问题是:我被请来协助解决负载测试问题。在大约 500 个并发用户时,登录过程将分解为重定向循环。IIS 记录异常,并显示消息"异步模块或处理程序已完成,而异步操作仍处于挂起状态"。一些搜索使我认为有人在滥用async void,但我对来源的快速搜索一无所获。

可悲的是,当我应该寻找更像async's*[^T]的东西时,我正在寻找async's*void(正则表达式搜索)(假设任务不是完全合格的..你明白了)。

我后来发现async override void onActionExecuting在基本控制器中。显然,这一定是问题所在,而且确实如此。修复它(使其暂时同步)解决了问题。

但它给我留下了一个问题:当调用代码永远无法等待它时,为什么您可以将覆盖标记为异步?

为什么 C# 允许进行覆盖异步

当基类(或接口)声明返回 Task 的虚拟方法时,只要返回 Task,就可以重写它。async 关键字只是对编译器将方法转换为状态机的提示。尽管编译器在您的方法上执行了黑魔法,但编译后的方法仍然返回一个 Task


至于void虚拟方法,您可以覆盖一个没有 async 关键字的方法(显然),并在其中启动一个非等待的任务。当您使用 async 关键字覆盖它并在正文中使用await时,就会发生这种情况。调用方不会等待创建的任务(因为"原始"签名void)。两种情况相似*:

public override void MyVirtualMethod()
{
    // Will create a non awaited Task (explicitly)
    Task.Factory.StartNew(()=> SomeTaskMethod());  
}
public override async void MyVirtualMethod()
{
    // Will create a non awaited Task (the caller cannot await void)
    await SomeTaskMethod();  
}

Stephen Cleary的文章对此有一些注释:

  • void 返回异步方法有一个特定的用途:使异步事件处理程序成为可能。
  • 您应该更喜欢异步任务而不是异步无效。

*SomeTaskMethod、底层框架、同步上下文和其他因素的实现可能会并且将导致上述每种方法的不同结果。

您可以重写async方法,因为异步不是方法签名的一部分。实际上,异步允许通过在方法中使用await关键字,方法是在内部创建状态机。
您可以在此处找到有关异步的更多信息:http://blog.sublogic.com/2012/05/14/async-不是-真的是方法签名的一部分/

async不是

"合同"的一部分。不幸的是,在我看来,这是一个实现细节,出现在错误的地方。

将返回Task的(非async)方法更改为async方法(反之亦然)是完全合法的,而不需要进行重大更改,也不需要调用方重新编译。

进一步表明它不是合约的一部分的是,您不允许在接口内将函数标记为async,并且在这里,完全可以用非async覆盖async,反之亦然。