Skip to content

CrazyPandaLimited/SimplePromises

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Описание

Предоставляет возможность использовать Promises для написания асинхронного кода. Реализация для использования в одном главном потоке, многопоточность не поддерживается! Имеет поддержку async/await

Гайд по написанию async методов

Зачем вам это может понадобиться Есть несколько причин, по которым имеет смысл использовать async:

Ваш метод выполняется не мгновенно/вычисляет результат не мгновенно. Например: делает http запрос, загружает ресурсы, выполняет анимацию (заканчивается вместе с ней), выполняет многопоточные вычисления. Собственно это основная причина для использования async =) А что вам это даст?

  • Более компактный код - не нужно явно подписываться на .Done, .Then и передавать параметры между коллбэками.
  • Возможность сделать catch исключения.
  • Возможность использовать using - упрощает управление объектами, которым нужно обязательное завершение.
  • Не ломает уже существующий код - async метод влияет только на реализацию метода, но не на пользователей, наружу выдается такая же IPandaTask, что и из обычного.

Как выполняется async

Ключевое слово 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 методов

Если метод не возвращает какой-то результат — используйте такую сигнатуру:

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. Чтобы создать асинхронный тест, нужно сделать следующее:

  1. Включить один из Compiler Define:
    • CRAZYPANDA_UNITYCORE_PROMISES_ENABLE_ASYNC_TESTS_EDITOR - для EditMode тестов
    • CRAZYPANDA_UNITYCORE_PROMISES_ENABLE_ASYNC_TESTS_PLAYMODE - для PlayMode тестов
  2. Создать метод, возвращающий IPandaTask, IPandaTask<T>, Task или Task<T>
  3. Пометить его атрибутом 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);
}

Собственная логика Update

Иногда в тестах может потребоваться имитировать UpdateLoop, чтобы созданные задачи завершались.

Если в тесте необходимо выполнять какие-то действия для завершения задач, то можно сделать так:

[ AsyncTest ]
public async IPandaTask TestMethod()
{
    TestSynchronizationContext.OnTick += () => UpdateYoutStuff(Params);
    await SomeTaskThatNeedTicking();
}

Обновления будут вызываться во время ожидания завершения задач. Отписываться от события не нужно. В каждом тесте можно подписывать свою логику обновления, и даже менять подписки в процессе выполнения теста.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages