#unity3d
#unity3d
Вопрос:
У меня есть сопрограмма следующим образом:
IEnumerator CountingCoroutine()
{
while (true)
{
DoSomething();
yield return new WaitForSeconds(1);
}
}
Мне нужно вызвать некоторую асинхронную функцию внутри нее. Если я написал это следующим образом, компилятор жалуется:
async IEnumerator CountingCoroutine()
{
while (true)
{
DoSomething();
await foo();
yield return new WaitForSeconds(1);
}
}
Какое правильное ожидание для вызова некоторых асинхронных функций в секунду?
PS Я мог бы отказаться от всей функции сопрограммы и использовать обновление с механизмом подсчета времени и пометить функцию обновления как асинхронную. Но я не уверен, безопасно ли это или рекомендуется.
Решение с рекомендациями от @derHugo:
IEnumerator CountingCoroutine()
{
while (true)
{
DoSomething();
var task = Task.Run(foo);
yield return new WaitUntil(()=> task.IsCompleted);
yield return new WaitForSeconds(1);
}
}
Ответ №1:
Вы могли бы использовать Task.IsCompleted
Нравится
private IEnumerator CountingCoroutine()
{
while (true)
{
DoSomething();
var task = Task.Run(foo);
yield return new WaitUntil(()=> task.IsCompleted);
}
}
которая запускает новую задачу, как только предыдущая завершена.
Вы могли бы расширить это, чтобы запускать новую задачу как можно раньше каждую секунду или не позднее, когда предыдущая задача завершается через секунду, например,
private IEnumerator CountingCoroutine()
{
var stopWatch = new StopWatch();
while (true)
{
DoSomething();
stopWatch.Restart();
var task = Task.Run(foo);
yield return new WaitUntil(()=> task.IsCompleted);
var delay = Mathf.Max(0, 1 - stopWatch.ElapsedMilliseconds * 1000);
// Delays between 1 frame and 1 second .. but only the rest that foo didn't eat up
yield return new WaitForSeconds(delay);
// or you could with a bit more calculation effort also
// even avoid the one skipped frame completely
if(!Mathf.Approximately(0, delay)) yield return new WaitForSeconds(delay);
}
}
Или, если вы не заботитесь о завершении и хотите вызывать это только так, как вы говорите каждую секунду, тогда просто сделайте
private IEnumerator CountingCoroutine()
{
while (true)
{
DoSomething();
Task.Run(foo);
yield return new WaitForSeconds(1);
}
}
Однако обратите внимание, что таким образом вы теряете весь контроль и никогда не узнаете, завершилась ли одна из этих задач и когда, и может быть запущено несколько одновременных задач / сбой и т. Д
В качестве альтернативы вы, вероятно, могли бы пойти другим путем и использовать что-то вроде Asyncoroutine) .. возможно, вам придется следить за тем, что происходит в фоновом потоке.
Комментарии:
1. Спасибо! На данный момент я использую что-то похожее на ваше второе решение
_ = foo();
. А также, как вы указали, этот способ может вызвать сложные конфликты. Я переделаю ее и остановлю сопрограмму подсчета времени и возобновлю, когда работа будет выполнена.2. обратите внимание, что простое использование
_ = foo();
можетfoo
привести к синхронному запуску, поскольку оно не ожидается, а сопрограмма не асинхронна3. Отмечено. Я только что понял ваше первое решение. Пожалуйста, посмотрите мои недавно отредактированные коды в вопросе. Я новичок в keyward
yield
. В моем понимании первый выход не будет блокировать другие коды вызывающего абонента, но блокирует все, что после этой строки. И когда задача будет выполнена, она будет подхвачена и запущена со следующей строки, в данном случае 2-го выхода. В этом случае этот цикл может занять 1 секунду плюс время выполнения foo() . Я прав?4. @ArtS да .. но я только что отредактировал ответ и добавил третий пример (теперь это второй ^^), где я вычитаю время, которое
foo
ушло на 1 секунду, и задерживаю только на остальное. Так что в любом случае это обязательно будет ждать, покаfoo
не будет сделано, но после этого только остальное время, но хотя бы один кадр5.
yield
работает немного по-другому: он вызывается каждый кадрUpdate
подпрограммой Unity. Однако, напримерWaitUntil
, само по себеIEnumerator
является просто возвращаемымtrue
, пока условие не будет полностью заполнено. Итак, простыми словами, скажем, что сопрограмма «застряла», пока не сможет перейти к следующей строке 😉
Ответ №2:
Если вам нужно использовать результат задачи, вот пример:
IEnumerator RefreshLobbyCoroutine()
{
var delay = new WaitForSecondsRealtime(2);
while (lobby != null)
{
// Run task and wait for it to complete
var t = Task.Run(async () => await Lobbies.Instance.GetLobbyAsync(lobby.Id));
yield return new WaitUntil(() => t.IsCompleted);
// Task is complete, get the result
lobby = t.Resu<
yield return delay;
}
}