Как вырваться из IAsyncEnumerable при итерации?

В C# 8 добавлена ​​поддержка асинхронных блоков итераторов, поэтому вы можете ждать и возвращать IAsyncEnumarator вместо IEnumerable:

public async IAsyncEnumerable<int> EnumerateAsync() {
    for (int i = 0; i < 10; i++) {
        yield return i;
        await Task.Delay(1000);
    }
}

С неблокирующим кодом, который выглядит следующим образом:

await foreach (var item in EnumerateAsync()) {
    Console.WriteLine(item);
}

Это приведет к тому, что мой код будет работать около 10 секунд. Однако иногда я хочу вырваться из await foreach до того, как все элементы потребляются. С breakТем не менее, нам нужно подождать, пока ток не ожидается Task.Delay закончил Как мы можем немедленно выйти из этого цикла, не ожидая каких-либо висячих асинхронных задач?

1 ответ

Использование CancellationToken это решение, так как это единственное, что может отменить Task.Delay в вашем коде. То, как мы получаем это в вашем IAsyncEnumerable это передать его в качестве параметра при его создании, поэтому давайте сделаем это:

public async IAsyncEnumerable<int> EnumerateAsync(CancellationToken cancellationToken = default) {
    for (int i = 0; i < 10; i++) {
        yield return i;
        await Task.Delay(1000, cancellationToken);
    }
}

С потребляющей стороны:

// In this example the cancellation token will be caneled after 2.5 seconds
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2.5));
await foreach (var item in EnumerateAsync(cts.Token)) {
    Console.WriteLine(item);
}

Конечно, это отменит перечисление после того, как 3 элемента были возвращены, но закончится TaskCanceledException выброшенный из Task.Delay, Изящно выйти из await foreach мы должны поймать это и сломать на стороне производства:

public async IAsyncEnumerable<int> EnumerateAsync(CancellationToken cancellationToken = default) {
    for (int i = 0; i < 10; i++) {
        yield return i;
        try {
            await Task.Delay(1000, cancellationToken);
        } catch (TaskCanceledException) {
            yield break;
        }
    }
}

Заметка

На данный момент это все еще в предварительном просмотре и может быть изменено. Если вы заинтересованы в этой теме, вы можете посмотреть обсуждение языковой команды C# о CancellationToken в IAsyncEnumeration,

Другие вопросы по тегам