简体   繁体   English

为什么 C# 编译器处理空异步任务方法与仅返回 Task.CompletedTask 不同?

[英]Why does the C# compiler handle empty async Task methods different to just return Task.CompletedTask?

Hello,你好,

Edit: the post was edited to not use empty methods to be more clear about what I want to know.编辑:该帖子被编辑为不使用空方法以更清楚地了解我想知道的内容。

Is there a reason why the C# compile does not compile a async Task method without using await to return Task.CompletedTask ? C# 编译不使用await返回Task.CompletedTask的情况下不编译async Task方法是否有原因?

Sometimes it is necessary to implement a method that returns a Task but it is not using await .有时有必要实现一个返回Task但没有使用await的方法。

I am wondering if there is a reason why the generated IL code is so different between that two methods:我想知道生成的 IL 代码在这两种方法之间是否存在如此不同的原因:

public async Task DoNothing()
{
    Console.WriteLine("nothing");
}
public Task DoNothing()
{
    Console.WriteLine("nothing");
    return Task.CompletedTask;
}

I created two Fiddles where the generated IL code can be viewed.我创建了两个 Fiddles,可以在其中查看生成的 IL 代码。
async Task : https://dotnetfiddle.net/UQuJUh async Taskhttps://dotnetfiddle.net/UQuJUh
Task.CompletedTask : https://dotnetfiddle.net/eRr4i1 Task.CompletedTaskhttps://dotnetfiddle.net/eRr4i1

So is there a reason why the compiler does not optimize the code to handle both cases the same way?那么编译器不优化代码以以相同方式处理这两种情况的原因是什么? Thank you.谢谢你。

There is a significant difference in the way exceptions are handled in those two pieces of code, which are therefore not equivalent :在这两段代码中处理异常的方式存在显着差异,因此它们是不等价的:

public async Task DoNothing()
{
    Console.WriteLine("nothing");
}

In the case that Console.WriteLine raised an exception, the exception is actually internally caught and stored inside the resulting Task , indicated by Task.IsFaulted .Console.WriteLine引发异常的情况下,异常实际上是在内部捕获并存储在结果Task中,由Task.IsFaulted指示。 The exception is only actually "thrown" when you use await on the task.只有当您在任务上使用await时,才会真正“抛出”异常。

public Task DoNothing()
{
    Console.WriteLine("nothing");
    return Task.CompletedTask;
}

This case however is fully synchronous;然而,这种情况是完全同步的; the potential exception from Console.WriteLine would be raised even before any task could be returned or processed.来自Console.WriteLine的潜在异常甚至在任何任务可以被返回或处理之前就会被引发。 I'd argue this is actually a bit less safe than the first option, since this behaviour might be unexpected.我认为这实际上比第一个选项更不安全,因为这种行为可能是出乎意料的。 Iterators show similar behaviour for the difference between yield break;迭代器对yield break; and return Array.Empty<object>();return Array.Empty<object>(); . .

Now your question originally asked about an actually empty method.现在您的问题最初询问了一个实际上是空的方法。 Well, it could be the case that the method is not actually empty;好吧,可能是该方法实际上不是空的; it may contain breakpoints in debug mode, injected code or perhaps auto-generated checks for the arguments, in the future.将来它可能包含调试模式下的断点、注入代码或可能为 arguments 自动生成的检查。 Even the empty try block is still generated for an empty method body:即使是空的方法体,仍然会生成空的try块:

  .try
  {
    IL_0000:  leave.s    IL_0019
  }  // end .try
  catch [mscorlib]System.Exception 
  {

Now I can imagine an exception in generating async method could be added to the compiler for the case of a truly empty method, but it would only complicate the code, for no (arguably) real gain.现在我可以想象,对于真正空的方法,可以将生成async方法的异常添加到编译器中,但这只会使代码复杂化,而没有(可以说)真正的收益。 There might be a point of having an empty async method (such as to give other programmers who might want to implement it in a future a hint), but then you don't need to care about performance that much to warrant such an optimization (and you have a trivial solution anyway).可能会有一个空的async方法(例如给其他可能想要在未来实现它的程序员一个提示),但是你不需要那么关心性能来保证这样的优化(无论如何你都有一个简单的解决方案)。

(Interesting side note: C# 11 might introduce automatic raising of ArgumentNullException from parameters marked with !! . At this time, the exception is actually raised outside of the async /iterator method's code, immediately after the call, so it could be argued that the method body, or what becomes of it, is still, technically, empty.) (有趣的旁注:C# 11 可能会从标有!!的参数中引入ArgumentNullException的自动引发。此时,异常实际上是在async /iterator 方法的代码之外,在调用之后立即引发的,因此可以说方法体,或者它的结果,在技术上仍然是空的。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM