← Назад к вопросам

Для каких типов операций лучше всего подходит асинхронность?

2.0 Middle🔥 241 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Асинхронность в C# - Когда и Почему

Асинхронность в .NET идеальна для операций ввода-вывода (I/O), когда поток может выпустить ресурсы и позволить другим операциям использовать их. Рассмотрим, для каких типов операций она лучше всего подходит.

1. Сетевые запросы (HTTP, API)

Это идеальный кейс для асинхронности. Пока ждёшь ответ от удалённого сервера, поток может обрабатывать другие запросы.

public class ApiClient
{
    private readonly HttpClient _httpClient;
    
    // ПЛОХО - синхронный, блокирует поток
    public User GetUserSync(int id)
    {
        var response = _httpClient.GetStringAsync(
            $"https://api.example.com/users/{id}"
        ).Result;  // .Result блокирует поток!
        
        return JsonConvert.DeserializeObject<User>(response);
    }
    
    // ХОРОШО - асинхронный
    public async Task<User> GetUserAsync(int id)
    {
        var response = await _httpClient.GetStringAsync(
            $"https://api.example.com/users/{id}"
        );  // Поток не блокируется
        
        return JsonConvert.DeserializeObject<User>(response);
    }
}

// Использование в ASP.NET Core
[ApiController]
public class UserController
{
    private readonly ApiClient _apiClient;
    
    [HttpGet("{id}")]
    public async Task<ActionResult<User>> GetUser(int id)
    {
        var user = await _apiClient.GetUserAsync(id);  // Не блокирует
        return Ok(user);
    }
}

Выигрыш: При 10,000 одновременных запросов с асинхронностью нужно ~50 потоков, а без неё нужно 10,000 потоков.

2. Работа с базами данных

Датабаза обработает запрос независимо от потока .NET. Асинхронность позволяет не держать поток занятым.

public class UserRepository : IUserRepository
{
    private readonly DbContext _dbContext;
    
    // ПЛОХО - синхронный, блокирует
    public User GetById(int id)
    {
        return _dbContext.Users
            .FirstOrDefault(u => u.Id == id);  // Поток ждёт ответа БД
    }
    
    // ХОРОШО - асинхронный
    public async Task<User> GetByIdAsync(int id)
    {
        return await _dbContext.Users
            .FirstOrDefaultAsync(u => u.Id == id);  // Поток свободен
    }
}

// В сервисе
public class UserService
{
    private readonly IUserRepository _repository;
    
    public async Task<UserDto> GetUserAsync(int id)
    {
        var user = await _repository.GetByIdAsync(id);
        return MapToDto(user);
    }
}

Почему это работает:

  • EF Core использует асинхронные драйверы БД
  • Поток может обработать другой запрос пока БД отвечает
  • SQL Server / PostgreSQL обрабатывают запросы параллельно

3. Файловые операции (File I/O)

Чтение/запись файлов - это операции ввода-вывода, которые могут заблокировать поток.

public class FileService
{
    // ПЛОХО - блокирует поток
    public string ReadFileSync(string path)
    {
        return File.ReadAllText(path);  // Ждёт дискового ввода-вывода
    }
    
    // ХОРОШО - асинхронный
    public async Task<string> ReadFileAsync(string path)
    {
        return await File.ReadAllTextAsync(path);  // Поток не занят
    }
}

// Пример с большими файлами
public async Task ProcessLargeFileAsync(string path)
{
    using var stream = File.OpenRead(path);
    using var reader = new StreamReader(stream);
    
    string line;
    while ((line = await reader.ReadLineAsync()) != null)
    {
        // Обработка строки
        await ProcessLineAsync(line);
    }
}

4. Работа с сокетами и реал-тайм коммуникацией

WebSocket, TCP сокеты - это явно I/O операции.

public class WebSocketService
{
    public async Task HandleConnectionAsync(WebSocket webSocket)
    {
        var buffer = new byte[1024 * 4];
        
        // ХОРОШО - асинхронное получение данных
        var result = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), 
            CancellationToken.None
        );  // Не блокирует другие соединения
        
        // Обработка сообщения
        var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
        
        // ХОРОШО - асинхронная отправка
        await webSocket.SendAsync(
            new ArraySegment<byte>(response), 
            WebSocketMessageType.Text, 
            true, 
            CancellationToken.None
        );
    }
}

5. Вызовы внешних сервисов (API, микросервисы)

public class OrderService
{
    private readonly HttpClient _httpClient;
    private readonly IPaymentGateway _paymentGateway;
    
    public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
    {
        // Асинхронные вызовы к разным сервисам
        var userTask = _httpClient.GetAsync($"api/users/{request.UserId}");
        var inventoryTask = _httpClient.GetAsync($"api/inventory/{request.ItemId}");
        
        // Параллельное выполнение
        await Task.WhenAll(userTask, inventoryTask);
        
        // Асинхронный платёж
        var paymentResult = await _paymentGateway.ProcessAsync(request.Amount);
        
        return new Order { /* ... */ };
    }
}

6. Работа с кэшем и Redis

public class CachedUserService
{
    private readonly IDistributedCache _cache;
    private readonly IUserRepository _repository;
    
    public async Task<User> GetUserAsync(int id)
    {
        // Асинхронное обращение к кэшу
        var cachedUser = await _cache.GetStringAsync($"user:{id}");
        
        if (!string.IsNullOrEmpty(cachedUser))
            return JsonConvert.DeserializeObject<User>(cachedUser);
        
        // Если нет в кэше - асинхронное обращение к БД
        var user = await _repository.GetByIdAsync(id);
        
        // Асинхронное сохранение в кэш
        await _cache.SetStringAsync(
            $"user:{id}", 
            JsonConvert.SerializeObject(user),
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
            }
        );
        
        return user;
    }
}

7. Message Queues и pub/sub

public class EventPublisher
{
    private readonly IConnection _rabbitConnection;
    
    public async Task PublishEventAsync<T>(T eventData) where T : class
    {
        using var channel = _rabbitConnection.CreateModel();
        
        var json = JsonConvert.SerializeObject(eventData);
        var bytes = Encoding.UTF8.GetBytes(json);
        
        // Асинхронная публикация события
        await Task.Run(() => channel.BasicPublish(
            exchange: "events",
            routingKey: typeof(T).Name,
            basicProperties: null,
            body: bytes
        ));
    }
}

public class EventSubscriber
{
    public async Task SubscribeAsync()
    {
        var channel = _connection.CreateModel();
        var consumer = new AsyncEventingBasicConsumer(channel);
        
        // Асинхронная обработка событий
        consumer.Received += async (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            
            await ProcessEventAsync(message);
        };
    }
}

Когда НЕ нужна асинхронность

Чисто вычислительные операции (CPU-bound)

// ПЛОХО - неправильное использование async
public async Task<int> CalculateFibonacciAsync(int n)
{
    return await Task.Run(() => Fibonacci(n));
}

// ХОРОШО - просто синхронный метод
public int CalculateFibonacci(int n)
{
    return Fibonacci(n);
}

// Если нужен параллелизм - используй Parallel или Task.Run
public int CalculateFibonacciParallel(int n)
{
    return Parallel.For(/* ... */);
}

Простые синхронные операции

// ПЛОХО - async для простой операции
public async Task<string> ConcatAsync(string a, string b)
{
    return await Task.FromResult($"{a}{b}");
}

// ХОРОШО
public string Concat(string a, string b)
{
    return $"{a}{b}";
}

Правило большого пальца (Rule of Thumb)

Используй асинхронность если:

├─ Сетевые запросы (HTTP)          → ДА
├─ Работа с БД                     → ДА
├─ Файловые операции               → ДА
├─ Работа с WebSocket/sockets      → ДА
├─ Message queues                  → ДА
├─ Вызовы внешних сервисов        → ДА
├─ Работа с Redis/кэшем            → ДА
├─ Чистые вычисления              → НЕТ
├─ Простые синхронные операции    → НЕТ
└─ Когда нет I/O операций         → НЕТ

Важные моменты

Используй async всей цепочкой

// ПЛОХО - блокирующий вызов в async методе
public async Task<User> GetUserAsync(int id)
{
    var user = _repository.GetByIdAsync(id).Result;  // Блокирует!
    return user;
}

// ХОРОШО - вся цепочка async
public async Task<User> GetUserAsync(int id)
{
    var user = await _repository.GetByIdAsync(id);
    return user;
}

Помни о контексте синхронизации

// Может привести к deadlock в UI приложениях
var result = asyncMethod().Result;  // Плохо!

// Правильно
var result = await asyncMethod();

Заключение

Асинхронность в .NET предназначена для операций ввода-вывода (I/O-bound): сетевые запросы, БД, файлы, сокеты, кэш. Она позволяет:

  • Обрабатывать больше одновременных операций
  • Эффективнее использовать ресурсы
  • Повышать пропускную способность приложения

Для CPU-bound операций асинхронность не поможет - нужен параллелизм (Parallel, Task.Run).

Для каких типов операций лучше всего подходит асинхронность? | PrepBro