在异步方法中ref和out参数

时间:2014-01-01 11:22:17

标签: c# asynchronous

有谁知道为什么async方法不允许有refout个参数?我已经做了一些或研究它,但我唯一能找到的是它与堆栈展开有关。

2 个答案:

答案 0 :(得分:77)

  

有谁知道为什么异步方法不允许有参数和参数?

不确定。想一想 - 异步方法通常几乎立即返回,早在大部分实际逻辑执行之前......就是异步完成的。因此,必须在第一个out表达式之前分配任何await参数,并且很可能必须对ref参数进行一些限制,以阻止它们在第一个之后被使用无论如何await表达,因为之后它们甚至可能无效。

考虑使用outref参数调用异步方法,使用参数的局部变量:

int x;
int y = 10;
FooAsync(out x, ref y);

FooAsync返回后,方法本身可以返回 - 因此这些局部变量将不再在逻辑上存在......但异步方法仍然可以有效地在其连续中使用它们。大问题。编译器可以创建一个新类来捕获变量,就像它对lambda表达式一样,但这会导致其他问题...除了其他任何事情,你可以改变本地变量在任意点通过方法,当continuation在不同的线程上运行时。奇怪的是至少可以说。

基本上,由于所涉及的时间安排,对out方法使用refasync参数没有意义。请使用包含您感兴趣的所有数据的返回类型。

如果您只对outref参数在第一个await表达式之前更改感兴趣,您可以随时将该方法拆分为两个:

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!
{
}

编辑:使用out获取Task<T>参数是可行的,并在方法中直接指定值,就像返回值一样。虽然这有点奇怪,但它不适用于ref参数。

答案 1 :(得分:12)

C#编译为CIL,CIL不支持此功能。

CIL本身没有asyncasync方法被编译为一个类,所有(使用过的)参数和局部变量都存储在类字段中,这样当调用该类的特定方法时,代码知道继续执行的位置和值变量有。

使用托管指针实现

refout参数,并且不允许托管指针类型的类字段,因此编译器无法保留传入的引用。

对类字段中托管指针的这种限制会阻止一些无意义的代码,如Jon Skeet的回答所述,因为类字段中的托管指针可能引用已返回的函数的局部变量。但是,这种限制非常严格,即使安全和正确使用也会被拒绝。 ref / out字段可以工作,如果它们引用另一个类字段,并且编译器确保始终包装使用ref / {传递的局部变量{1}}在一个类中(就像它已经知道该怎么做)。

因此,C#根本无法解决CIL施加的限制。即使C#设计师想要允许它(我不是说他们这样做),他们也不能。