Введение:
Современные приложения работают в условиях, где миллисекунды задержки могут стоить тысяч долларов упущенной выгоды. Блокирующие операции — роскошь, которую разработчики больше не могут себе позволить.
Асинхронность в C# — это не просто модный термин, а фундаментальный подход к созданию отзывчивых, масштабируемых и эффективных приложений.
Почему это важно?
-
Серверные приложения должны обрабатывать тысячи одновременных запросов без простоев.
-
Мобильные и десктопные приложения не могут позволить себе «зависание» UI.
-
Микросервисы требуют эффективного взаимодействия с внешними API и базами данных.
В этой статье мы разберём:
-
Продвинутые паттерны (каналы, асинхронные потоки, семфоры)
-
Антипаттерны, которые приводят к deadlock и утечкам памяти
-
Оптимизации для высоконагруженных систем (ValueTask, ConfigureAwait)
-
Реальные кейсы из практики (обработка очередей, распределённые транзакции)
Это не теоретическое руководство — только проверенные в боях техники, которые используют в таких компаниях, как Microsoft, Stack Overflow и JetBrains.
Готовы выйти за рамки async/await
и освоить асинхронность профессионального уровня? Поехали!
1. Базовые Принципы
1.1. Асинхронный HTTP-запрос
public async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
1.2. Параллельное выполнение
public async Task ProcessMultipleRequestsAsync(IEnumerable<string> urls)
{
var tasks = urls.Select(FetchDataAsync).ToList();
var results = await Task.WhenAll(tasks);foreach (var content in results)
{
Console.WriteLine(content[..50]);
}
}
2. Продвинутые Сценарии
2.1. ValueTask для оптимизации
public async ValueTask<int> CalculateAsync(int input)
{
if (input < 0)
return 0;await Task.Delay(100);
return input * 2;
}
2.2. Отмена операции
public async Task LongRunningOperationAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(1000, token);
}
}
3. Обработка Ошибок
public async Task SafeOperationAsync()
{
try
{
await RiskyMethodAsync();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine(«Ресурс не найден»);
}
catch (OperationCanceledException)
{
Console.WriteLine(«Операция отменена»);
}
}
4. Производительность
ConfigureAwait
public async Task<string> GetDataAsync()
{
var data = await SomeIoOperationAsync().ConfigureAwait(false);
return ProcessData(data);
}
5. Продвинутые паттерны асинхронности
5.1. Асинхронные потоки (IAsyncEnumerable)
public async IAsyncEnumerable<string> FetchPaginatedDataAsync(int startPage)
{
for (int page = startPage; page < startPage + 5; page++)
{
var data = await GetPageAsync(page);
yield return data;
}
}public async Task ProcessStreamAsync()
{
await foreach (var chunk in FetchPaginatedDataAsync(1))
{
Console.WriteLine(chunk);
}
}
5.2. Ограничение параллелизма (SemaphoreSlim)
private static readonly SemaphoreSlim _semaphore = new(3);
public async Task ProcessWithConcurrencyLimitAsync()
{
await _semaphore.WaitAsync();
try
{
await HeavyOperationAsync();
}
finally
{
_semaphore.Release();
}
}
5.3. Каналы (System.Threading.Channels)
private readonly Channel<string> _channel = Channel.CreateUnbounded<string>();
public async Task ProducerAsync()
{
for (int i = 0; i < 10; i++)
{
await _channel.Writer.WriteAsync($»Message {i}»);
}
_channel.Writer.Complete();
}public async Task ConsumerAsync()
{
await foreach (var message in _channel.Reader.ReadAllAsync())
{
Console.WriteLine($»Received: {message}»);
}
}
6. Антипаттерны и как их избежать
6.1. Deadlock из-за .Result
или .Wait()
❌ Плохо:
public string GetDataSync()
{
return FetchDataAsync().Result; // Может привести к дедлоку!
}
✅ Хорошо: