Применяли ли вы асинхронное программирование в C#?

«Применяли ли вы асинхронное программирование в C#?» — вопрос из категории Многопоточность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, асинхронность — стандартный подход для создания отзывчивых и масштабируемых приложений в .NET.

Основные принципы и практики:

  • Ключевые слова async/await: Позволяют писать неблокирующий код, который выглядит как синхронный.
  • Возвращаемый тип Task или Task<T>: Метод, содержащий await, должен возвращать Task.

Типичные сценарии использования:

  1. Веб-приложения (ASP.NET Core): Все контроллеры и middleware по умолчанию асинхронны, чтобы освобождать потоки пула во время операций ввода-вывода (запросы к БД, внешние API, файловая система).

    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        var product = await _dbContext.Products
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Id == id); // Асинхронный запрос к БД
    
        if (product == null)
            return NotFound();
    
        return Ok(product);
    }
  2. Клиентские приложения (WPF, WinUI): Сохранение отзывчивости UI-потока.

    private async void LoadDataButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            IsLoading = true;
            // Асинхронная операция не блокирует UI
            var data = await _httpClient.GetFromJsonAsync<List<Item>>("api/items");
            ItemsCollection = new ObservableCollection<Item>(data);
        }
        catch (HttpRequestException ex)
        {
            // Обработка ошибок
        }
        finally
        {
            IsLoading = false;
        }
    }
  3. Параллельное выполнение независимых операций:

    public async Task<HomePageViewModel> LoadHomePageDataAsync()
    {
        var userTask = _userService.GetCurrentUserAsync();
        var newsTask = _newsService.GetLatestNewsAsync(10);
        var weatherTask = _weatherService.GetForecastAsync();
    
        // Все задачи запускаются параллельно и ожидаются вместе
        await Task.WhenAll(userTask, newsTask, weatherTask);
    
        return new HomePageViewModel
        {
            User = await userTask,
            News = await newsTask,
            Forecast = await weatherTask
        };
    }

Важные замечания:

  • Не использовать async void, кроме обработчиков событий.
  • Избегать Task.Wait() и Task.Result в асинхронном коде — это приводит к deadlock'ам.
  • Настраивать ConfigureAwait(false) в библиотечном коде, где не требуется контекст синхронизации.
  • Профилировать код на предмет чрезмерного создания задач или блокировок.