异步方法中的 ref 和 out 参数
Posted
技术标签:
【中文标题】异步方法中的 ref 和 out 参数【英文标题】:ref and out arguments in async method 【发布时间】:2021-07-03 04:43:43 【问题描述】:有谁知道为什么async
方法不允许有ref
和out
参数?我对此进行了一些研究,但我唯一能找到的是它与堆栈展开有关。
【问题讨论】:
我想你可以在这里找到答案***.com/questions/18716928/… 看看来自social.msdn.microsoft.com/Forums/vstudio/en-US/…的servy42的回答 social.msdn.microsoft.com/Forums/en-US/… 相关:How to write an async method with out parameter? 【参考方案1】:有谁知道为什么异步方法不允许有 ref 和 out 参数?
当然。想一想 - 一个异步方法通常返回 几乎立即,远在大多数实际逻辑执行之前......这是异步完成的。因此,任何out
参数都必须在第一个await
表达式之前分配,并且很可能必须对ref
参数进行一些限制,以阻止它们在第一个await
表达式之后使用,因为在那之后它们甚至可能无效。
考虑使用out
和ref
参数调用异步方法,并使用局部变量作为参数:
int x;
int y = 10;
FooAsync(out x, ref y);
FooAsync
返回后,方法本身可以返回 - 因此这些局部变量在逻辑上将不再存在......但异步方法仍然可以有效地在其延续中使用它们。大问题。编译器可以创建一个新类来以与 lambda 表达式相同的方式捕获变量,但这会导致其他问题......当延续在不同的线程上运行时,通过方法的任意点。至少可以说很奇怪。
基本上,由于涉及时间问题,对async
方法使用out
和ref
参数是没有意义的。请改用包含您感兴趣的所有数据的返回类型。
如果您只对在第一个 await
表达式之前更改的 out
和 ref
参数感兴趣,则始终可以将方法一分为二:
public Task<string> FooAsync(out int x, ref int y)
// Assign a value to x here, maybe change y
return FooAsyncImpl(x, y);
private async Task<string> FooAsyncImpl(int x, int y) // Not ref or out!
编辑:使用Task<T>
使用out
参数并像返回值一样直接在方法内分配值是可行的。不过这有点奇怪,而且它不适用于ref
参数。
【讨论】:
@hvd:是的,它可能会这样做 - 将编辑以将其包括在内。虽然它无法将一个out
参数作为另一个out
参数的参数传递...
当然,这很好。它可以传递传入的引用,但是如果 that 引用了未包装在类中的局部变量,您仍然会遇到同样的问题。
我想我弄错了,如果不扩展 CIL 是完全不可能的。
我不明白,你是说Action<T>
而不是Task<T>
?如果是这样,好主意。
@hvd:不,我是说任务。将其视为另一个返回值 - 直到整个方法完成,任务才会完成,此时该值将是方法中分配的最后一个值。有点奇怪,但可行。【参考方案2】:
C#编译成CIL,CIL不支持。
CIL 本身没有async
。 async
方法被编译成一个类,所有(使用的)参数和局部变量都存储在类字段中,这样当调用该类的特定方法时,代码就知道在哪里继续执行以及变量有什么值.
ref
和out
参数使用托管指针实现,不允许托管指针类型的类字段,所以编译器无法保留传入的引用。
对类字段中托管指针的这种限制可以防止一些无意义的代码,正如 Jon Skeet 的回答中所解释的那样,因为类字段中的托管指针可能引用已经返回的函数的局部变量。然而,这个限制是如此严格,以至于即使是安全的和正确的使用也会被拒绝。 ref
/out
字段可以工作,如果它们引用另一个类字段,并且编译器确保始终将使用 ref
/out
传递的局部变量包装在一个类中(就像它已经知道该怎么做)。
因此,C# 根本无法解决 CIL 施加的限制。即使 C# 设计者想要允许它(我不是说他们允许),他们也不能。
【讨论】:
以上是关于异步方法中的 ref 和 out 参数的主要内容,如果未能解决你的问题,请参考以下文章