Можно ли рекурсивно вызвать асинхронную функцию, не переполняя стек?



Поскольку возвращаемый сайт асинхронной функции не является вызывающим, я предполагаю, что это работает, но решил проверить, что это безопасно на всякий случай. Если это не так, то почему это переполняет стек?



static async Task CheckAsync(TimeSpan recursiveTimer)
{
// do some work

await Task.Delay(recursiveTimer);
CheckAsync(recursiveTimer);
}


Править:
Я решил просто попробовать его - похоже, он не переполняет стек (сейчас он работает на моей машине - в настоящее время он находится на вызове 210 000). Моя предполагаемая причина заключается в том, что возвращаемый сайт функции CheckAsync на самом деле не CheckAsync, а где-то в асинхронная сантехника. Поэтому, когда CheckAsync вызывает CheckAsync, он фактически не добавляет в стек вызовов через обычный механизм вызова функции, а вместо этого помещает функцию в качестве объекта в некоторую очередь async "для выполнения", которая выполняется через некоторый другой поток, управляющий асинхронными функциями.



Для любого, кто хорошо знает этот механизм: звучит ли это правильно?

508   2  

2 ответов:

Причина, по которой он работает для вас, заключается не в том, как называется CheckAsync, а в том, что вы ожидаете результата Task.Delay. Это всегда будет возвращать" еще не завершенную " задачу, поэтому ожидание ее запланирует продолжение. Это продолжение будет запущено в фактически пустом стеке, поэтому не имеет значения, что вы затем сделаете рекурсивный вызов.

Теперь я думаю, что у вас все еще есть утечка памяти, потому что IIRC фреймворк будет отслеживать "логический стек", который будет становись все больше и больше... но это будет храниться в куче и расширяться, пока у вас не закончится память.

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

static async Task CheckAsync(TimeSpan recursiveTimer)
{
    // Whatever
    await Task.FromResult(5);
    CheckAsync(recursiveTimer);
}

В этот момент, предполагая, что код в "whatever" ничего не ожидает, вы получите полностью синхронный код, просто используя Task для отслеживания завершения и исключений.

Я, конечно, не рекомендовал бы это в качестве шаблона для многократного выполнения работы (отчасти из-за памяти утечка, о которой я упоминал), но я надеюсь, что это объясняет, почему вы не получаете переполнение стека.

Причина, по которой он переполняется, заключается в том, что переполняет стек.

10 static async Task CheckAsync(TimeSpan recursiveTimer)
20 {
30    // do some work
40    await Task.Delay(recursiveTimer);
50    CheckAsync(recursiveTimer);
60 }

Выполнение кода будет идти

10 20 30 40 50 60                         //CheckAsync(recursiveTimer)
            10 20 30 40 50 60             //CheckAsync(CheckAsync(recursiveTimer))
                        10 20 30 40 50 60 //CheckAsync(CheckAsync(CheckAsync(recursiveTimer))

Содержание этого метода должно быть примерно таким:

while(doCheck){
  await Task.Delay(recursiveTimer);
  CheckAsync(recursiveTimer);
}

Предполагая, что вы хотите ждать после каждого асинхронного вызова и делать это, пока doCheck истинно. Я предполагаю, что вы измените значениеdoCheck в другом потоке.

Взгляните на: лучшие практики асинхронного программирования

Comments

    Ничего не найдено.