Кто-нибудь знает, почему методы async
не имеют аргументов ref
и out
? Я немного поработал или научился этому, но единственное, что я мог найти, это то, что он связан с распаковкой стека.
Аргументы 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();