Аргументы ref и out в асинхронном методе

Кто-нибудь знает, почему методы async не имеют аргументов ref и out? Я немного поработал или научился этому, но единственное, что я мог найти, это то, что он связан с распаковкой стека.

Ответ 1

Кто-нибудь знает, почему методы async не могут иметь аргументы ref и out?

Конечно. Подумайте об этом - метод async обычно возвращается почти сразу, задолго до того, как выполняется большая часть фактической логики... которая выполняется асинхронно. Таким образом, любые параметры out должны быть назначены перед первым выражением await, и, возможно, должно быть какое-то ограничение на параметры ref, чтобы они не могли использоваться после первого выражения await так как после этого они могут быть даже недействительными.

Рассмотрим метод async с параметрами out и ref, используя локальные переменные для аргументов:

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

После возврата FooAsync сам метод может вернуться - так что локальные переменные больше не будут логически существовать... но метод async все равно сможет эффективно использовать их в своих продолжениях. Большие проблемы. Компилятор может создать новый класс для захвата переменной таким же образом, как и для лямбда-выражений, но это может вызвать другие проблемы... кроме всего прочего, вы можете изменить локальную переменную в произвольных точках с помощью метода, когда продолжения выполняются в другом потоке. Как ни странно.

В принципе, нет смысла использовать параметры out и ref для методов async из-за задействованного времени. Используйте тип возврата, который включает все интересующие вас данные.

Если вас интересуют параметры out и ref, изменяющиеся до первого выражения 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!
{
}

EDIT: было бы возможно иметь параметры out, используя Task<T>, и присваивать значение непосредственно в методе точно так же, как возвращаемые значения. Было бы немного странно, и это не сработало бы для параметров ref.

Ответ 2

С# скомпилирован в CIL, и CIL не поддерживает это.

CIL не имеет async изначально. Методы async скомпилированы для класса, а все (используемые) параметры и локальные переменные хранятся в полях классов, поэтому, когда вызывается конкретный метод этого класса, код знает, где продолжать выполнение и какие значения имеют переменные.

Параметры

ref и out реализуются с использованием управляемых указателей, а поля классов управляемого типа указателя не допускаются, поэтому компилятор не может сохранить переданную ссылку.

Это ограничение на управляемые указатели в полях классов предотвращает некоторый бессмысленный код, как объясняется в ответе Джона Скита, поскольку управляемый указатель в поле класса может ссылаться на локальную переменную из функции, которая уже возвращена. Однако это ограничение настолько строгое, что даже безопасное и иное правильное использование отклонено. Поля ref/out могут работать, если они ссылаются на другое поле класса, и компилятор всегда должен обернуть локальные переменные, переданные с помощью ref/out в классе (например, он уже знает, как это сделать).

Таким образом, С# просто не имеет возможности обойти ограничения, налагаемые CIL. Даже если разработчики С# хотят разрешить это (я не говорю, что они делают), они не могут.

Ответ 3

public async Task<(Object, int)> GetAnyThing()
{
     var MyObject = await http.GetAsync("MyWAPI");
     int i = int.Parse(response.header.GetValues("myHeader"));
    return (MyObject, i);
 }

использование:

 (xObject, intVar) = await GetAnyThing();