Асинхронные стримы

Последнее обновление: 11.02.2022

Начиная с версии C# 8.0 в C# были добавлены асинхронные стримы, которые упрощают работу с потоками данных в асинхронном режиме. Хотя асинхронность в C# существует уже довольно давно, тем не менее асинхронные методы до сих пор позволяли получать один объект, когда асинхронная операция была готова предоставить результат. Для возвращения нескольких значений в C# могут применяться итераторы, но они имеют синхронную природу, блокируют вызывающий поток и не могут использоваться в асинхронном контексте. Асинхронные стримы обходят эту проблему, позволяя получать множество значений и возвращать их по мере готовности в асинхронном режиме.

По сути асинхронный стрим представляет метод, который обладает тремя характеристиками:

  • метод имеет модификатор async

  • метод возвращает объект IAsyncEnumerable<T>. Интерфейс IAsyncEnumerable определяет метод GetAsyncEnumerator, который возвращает IAsyncEnumerator:

    public interface IAsyncEnumerable<out T>
    {
    	IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
    }
    
    public interface IAsyncEnumerator<out T> : IAsyncDisposable
    {
    	T Current { get; }
    	ValueTask<bool> MoveNextAsync();
    }
    public interface IAsyncDisposable
    {
        ValueTask DisposeAsync();
    }
    
  • метод содержит выражения yield return для последовательного получения элементов из асинхронного стрима

Фактически асинхронный стрим объединяет асинхронность и итераторы. Рассмотрим простейший пример:

await foreach (var number in GetNumbersAsync())
{
    Console.WriteLine(number);
}

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

Итак, метод GetNumbersAsync() фактически и представляет асинхронный стрим. Этот метод является асинхронным. Его возвращаемый тип - IAsyncEnumerable<int>. А его суть сводится к тому, что он возвращает с помощью yield return каждые 100 миллисекунд некоторое число. То есть фактически метод должен вернуть 10 чисел от 0 до 9 с промежутком в 100 миллисекунд.

Для получения данных из стрима в методе Main используется цикл foreach:

await foreach (var number in GetNumbersAsync())

Важно отметить, что он предваряется оператором await. В итоге, каждый раз когда асинхронный стрим будет возвращать очередное число, цикл будет его получать и выводить на консоль.

Где можно применять асинхронные стримы? Асинхронные стримы могут применяться для получения данных из какого-нибудь внешнего хранилища. Например, пусть имеется следующий класс некоторого хранилища:

class Repository
{
	string[] data = { "Tom", "Sam", "Kate", "Alice", "Bob" };
	public async IAsyncEnumerable<string> GetDataAsync()
	{
		for (int i = 0; i < data.Length; i++)
		{
			Console.WriteLine($"Получаем {i + 1} элемент");
			await Task.Delay(500);
			yield return data[i];
		}
	}
}

Для упрощения примера данные здесь представлены в виде простого внутреннего массива строк. Для имитации задержки в получении применяется метод Task.Delay.

Получим эти данные в программе:

Repository repo = new Repository();
IAsyncEnumerable<string> data = repo.GetDataAsync();
await foreach (var name in data)
{
    Console.WriteLine(name);
}

class Repository
{
    string[] data = { "Tom", "Sam", "Kate", "Alice", "Bob" };
    public async IAsyncEnumerable<string> GetDataAsync()
    {
        for (int i = 0; i < data.Length; i++)
        {
            Console.WriteLine($"Получаем {i + 1} элемент");
            await Task.Delay(500);
            yield return data[i];
        }
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850