V3209. Unity Engine. Using await on 'Awaitable' object more than once can lead to exception or deadlock, as such objects are returned to the pool after being awaited.
Анализатор обнаружил более одного использования одного и того же 'UnityEngine.Awaitable' объекта с оператором 'await'. В целях оптимизации 'Awaitable' объекты хранятся в пуле-объектов. При использовании 'await' объект 'Awaitable' возвращается в пул. После этого при повторном применении 'await' к этой же ссылке объекта будет выброшено исключение, в некоторых случаях также возможна взаимная блокировка.
Рассмотрим синтетический пример:
async Awaitable<bool> AwaitableFoo() { .... }
async Awaitable ExampleFoo()
{
Awaitable<bool> awaitable = AwaitableFoo();
if (await awaitable)
{
var result = await awaitable;
....
}
}
В этом коде будет выброшено исключение (или возникнет взаимная блокировка) при инициализации переменной 'result' значением, полученным при использовании 'await' к 'awaitable'. Это произойдёт из-за того, что ранее 'await' уже применялся к 'awaitable' в условии условной конструкции.
Чтобы обезопасить этот код, следует избегать записи 'Awaitable' в переменную. Вместо этого можно переиспользовать значение, полученное после вызова 'AwaitableFoo()' c использованием 'await':
async Awaitable<bool> AwaitableFoo() { .... }
async Awaitable ExampleFoo()
{
bool value = await AwaitableFoo();
if (value)
{
var result = value;
....
}
}
Или повторно выполнять вызов с 'await' непосредственно метода 'AwaitableFoo' там, где это требуется:
async Awaitable<bool> AwaitableFoo() { .... }
async Awaitable ExampleFoo()
{
if (await AwaitableFoo())
{
var result = await AwaitableFoo();
....
}
}
Такое решение также является корректным, ведь при каждом вызове 'AwaitableFoo()' будет возвращён новый объект 'Awaitable'.
Стоит отметить, что возможны и менее очевидные случаи возникновения проблемы. Например, когда повторное применение 'await' к значению 'Awaitable' происходит внутри другого метода, которому это значение было передано в качестве аргумента:
async Awaitable<bool> AwaitableFoo() { .... }
async Awaitable<Result> GetResult(Awaitable<bool> awaitable)
{
if (await awaitable){ .... } // <=
else { .... }
}
async Awaitable ExampleFoo()
{
Awaitable<bool> awaitable = AwaitableFoo();
if (await awaitable) // <=
{
....
}
var result = await GetResult(awaitable); // <=
}
Решения проблемы в таких случаях полностью аналогичны решениям, описанным ранее.