Предоставляет возможность использовать Promises для написания асинхронного кода. Реализация для использования в одном главном потоке, многопоточность не поддерживается! Имеет поддержку async/await
Зачем вам это может понадобиться Есть несколько причин, по которым имеет смысл использовать async:
Ваш метод выполняется не мгновенно/вычисляет результат не мгновенно. Например: делает http запрос, загружает ресурсы, выполняет анимацию (заканчивается вместе с ней), выполняет многопоточные вычисления. Собственно это основная причина для использования async =) А что вам это даст?
- Более компактный код - не нужно явно подписываться на .Done, .Then и передавать параметры между коллбэками.
- Возможность сделать catch исключения.
- Возможность использовать using - упрощает управление объектами, которым нужно обязательное завершение.
- Не ломает уже существующий код - async метод влияет только на реализацию метода, но не на пользователей, наружу выдается такая же IPandaTask, что и из обычного.
Ключевое слово async указывает, что ваш метод должен быть скомпилирован как асинхронный. Такой метод превратится в конечный автомат, каждое состояние которого будет соответствовать блоку кода между вызовами await.
В принципе такие методы очень похожи на корутины, но с возможностью вернуть результат и обрабатывать исключения. Асинхронные методы по умолчанию не создают никаких потоков и не используют ThreadPool.
Чтобы не ломать голову, проще посмотреть на примере, как это будет выполнятся:
// метод, который загружает страницу по http и сохраняет в файл
// используем ключевое слово async, чтобы компилятор разрешил нам использовать await
// возвращаем IPandaTask, чтобы мы могли сделать await этого метода при вызове
public async IPandaTask LoadWebPage( string url, string filename )
{
// начиная отсюда, код будет выполнятся сразу же после вызова метода
var client = new HttpClient();
// здесь синхронная часть закончится в тот момент, когда мы вызовем GetStreamAsync
// GetStreamAsync вернет нам Task, мы подпишемся на его завершение и будем ждать
// в этот момент завершается вызов метода LoadWebPage и происходит возврат IPandaTask
// когда GetStreamAsync завершится, мы заберем у него результат и продолжим выполнение
var stream = await client.GetStreamAsync( url );
using( var file = File.OpenWrite( filename ) )
{
// здесь наше выполнение снова прервется до окончания CopyToAsync
await stream.CopyToAsync( file );
// здесь мы закроем файл, т.к. у нас есть using
}
// здесь мы завершим IPandaTask, который вернули наружу
}
Пример того, как вызывать такой метод:
// для вызывающего метода снова используем async
public async IPandaTask LoadPanda()
{
// здесь мы не используем await, т.к. хотим сохранить IPandaTask
IPandaTask task = LoadWebPage( "http://crazypanda.ru", "crazypanda.html" );
// мы попадаем сюда после вызова метода GetStreamAsync внутри LoadWebPage
// дожидаемся окончания загрузки
await task;
// здесь мы завершим IPandaTask, который вернули наружу
}
Если метод не возвращает какой-то результат — используйте такую сигнатуру:
async IPandaTask Method()
{
/* Do stuff */
}
Если метод возвращает результат — используйте такую сигнатуру:
async IPandaTask<string> Method()
{
/* Do stuff */
return "String result";
}
Если вам нужно передать метод в виде Action<...> или подписать на event — используйте такую сигнатуру:
async void Method()
{
try
{
/* Do stuff */
}
catch
{
/* Handle Exception */
}
}
Используйте async void ТОЛЬКО ЕСЛИ ПЕРВЫЕ ДВА ВАРИАНТА НЕВОЗМОЖНЫ!
В таком виде вы не сможете сделать await, и соответственно, не сможете обработать исключение. Поэтому исключение нужно обрабатывать внутри самого метода.
Если вы хотите запустить задачу при старте MonoBehaviour — можете сделать Start асинхронным
async IPandaTask Start()
{
}
Учитывайте, что каждый вызов асинхронного метода создает отдельный поток выполнения: Если вы делаете какие-то обновления из асинхронного метода, и вызовете его еще раз, пока первый не завершился, то у вас будут 2 метода, которые делают одно и то же.
По сути вызов асинхронного метода похож на StartCoroutine()
Если вы хотите обрабатывать исключения — используйте try/catch:
try
{
await SomeMethod()
}
catch( ArgumentException e )
{
// Do stuff or throw
}
Если вы хотите подождать некоторое время — используйте
PandaTasksUtilitys.Delay():
await PandaTasksUtilitys.Delay(1000);
await PandaTasksUtilitys.Delay(TimeSpan.FromSeconds(1));
Выполнение продолжится не раньше, чем через указанный промежуток времени.
Если вы хотите подождать до выполнения некоторого условия — используйте
PandaTasksUtilitys.WaitWhile():
await PandaTasksUtilitys.WaitWhile(() => myValue == false);
Аналогичная корутина выглядела бы так:
while( myValue == false )
{
yield return null;
}
Если хотите иметь возможность отменять задачу — используйте CancellationToken:
async IPandaTask MyMethod(CancellationToken token)
{
while ( CanDoStuff() )
{
token.ThrowIfCancellationRequested();
await DoStuff();
}
}
CancellationTokenSource cancellationSource = new CancellationTokenSource()
var task = MyMethod(cancellationSource.Token);
cancellationSource.Cancel();
await task; // тут будет выброшено исключение OperationCanceledException
Если хотите выполнить работу в другом потоке — используйте Task.Run:
await Task.Run( () => JObject.Parse( HeavyJson) );
При этом важно понимать, что:
- Это НЕ БУДЕТ работать на WebGL. Вы не получите никаких ошибок, просто функция не будет выполнена.
- Вы НЕ ДОЛЖНЫ изменять поля и свойства класса из метода, запущенного таким образом. Возвращать результат следует через return.
Для асинхронных тестов есть специальный атрибут AsyncTest. Чтобы создать асинхронный тест, нужно сделать следующее:
- Включить один из Compiler Define:
- CRAZYPANDA_UNITYCORE_PROMISES_ENABLE_ASYNC_TESTS_EDITOR - для EditMode тестов
- CRAZYPANDA_UNITYCORE_PROMISES_ENABLE_ASYNC_TESTS_PLAYMODE - для PlayMode тестов
- Создать метод, возвращающий IPandaTask, IPandaTask<T>, Task или Task<T>
- Пометить его атрибутом AsyncTest. При необходимости задать таймаут в атрибуте. По умолчанию он равен 10 сек.
В остальном они ничем не отличаются от обычных.
Примеры тестов:
[ AsyncTest ]
public async IPandaTask TestMethod()
{
await SomeAsyncOperation(5, 100);
}
[ AsyncTest(2.5) ] // timeout of 2.5 seconds
public Task TestMethod2()
{
return OtherAsyncTaskMethod(1, 2);
}
Иногда в тестах может потребоваться имитировать UpdateLoop, чтобы созданные задачи завершались.
Если в тесте необходимо выполнять какие-то действия для завершения задач, то можно сделать так:
[ AsyncTest ]
public async IPandaTask TestMethod()
{
TestSynchronizationContext.OnTick += () => UpdateYoutStuff(Params);
await SomeTaskThatNeedTicking();
}
Обновления будут вызываться во время ожидания завершения задач. Отписываться от события не нужно. В каждом тесте можно подписывать свою логику обновления, и даже менять подписки в процессе выполнения теста.