0Pricing
C# Academy · Урок

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

Разберитесь в SynchronizationContext, используйте ConfigureAwait(false) в коде библиотек и избегайте взаимных блокировок в асинхронных потоках выполнения.

«ConfigureAwait и контекст синхронизации» — бесплатный урок C# Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Что такое SynchronizationContext?

SynchronizationContext управляет планированием кода продолжения после await. В приложениях с пользовательским интерфейсом (WPF, WinForms) он гарантирует, что обновления интерфейса выполняются в потоке пользовательского интерфейса. В ASP.NET Core контекст синхронизации отсутствует (его удалили). Понимание этого необходимо для написания корректного асинхронного кода.

Как работает ConfigureAwait

.ConfigureAwait(false) указывает механизму ожидания НЕ захватывать текущий SynchronizationContext и не возобновлять выполнение в нём. Продолжение вместо этого выполняется в любом доступном потоке пула потоков.

// Default: captures context (resumes on original context)
var data = await FetchDataAsync();
UpdateUI(data); // runs on UI thread — correct for UI apps

// ConfigureAwait(false): doesn't capture context
var data2 = await FetchDataAsync().ConfigureAwait(false);
// Runs on any thread — DON'T access UI thread-only objects here

Классический шаблон взаимной блокировки

Синхронная блокировка задачи Task с помощью .Result или .Wait() при удержании контекста синхронизации приводит к классической взаимной блокировке: продолжение ожидает контекст, который заблокирован в ожидании этого продолжения.

// DEADLOCK in WPF/WinForms or ASP.NET classic:
string result = GetDataAsync().Result; // blocks UI thread
// GetDataAsync() tries to resume on UI thread after await
// UI thread is blocked by .Result
// DEADLOCK!

// Fix 1: await properly (never block)
string result = await GetDataAsync();

// Fix 2: use ConfigureAwait(false) in the library
public async Task<string> GetDataAsync()
    => await client.GetStringAsync(url).ConfigureAwait(false);

ConfigureAwait(false) в библиотеках

Код библиотеки, не зависящий от контекста, должен всегда использовать ConfigureAwait(false) при каждом ожидании. Это предотвращает взаимные блокировки, когда вызывающий код использует библиотеку в среде, зависящей от контекста.

// Library code — always ConfigureAwait(false)
public async Task<User?> GetUserAsync(int id)
{
    var json = await client
        .GetStringAsync($"/users/{id}")
        .ConfigureAwait(false);

    var user = JsonSerializer.Deserialize<User>(json);
    return user;
}

// Application code (has context) — leave ConfigureAwait default
public async Task LoadUserProfileAsync(int id)
{
    var user = await userService.GetUserAsync(id); // context captured
    ProfileLabel.Text = user?.Name; // safe to update UI
}

ASP.NET Core: нет SynchronizationContext

ASP.NET Core намеренно не использует SynchronizationContext. Продолжения выполняются в пуле потоков. Технически ConfigureAwait(false) ничего не меняет, но это по-прежнему хорошая привычка для кода, который используется в других платформах.

// In ASP.NET Core, both are equivalent:
var data = await service.GetAsync();                      // fine
var data2 = await service.GetAsync().ConfigureAwait(false); // also fine

// Context-free means: no deadlock risk from .Result in ASP.NET Core
// BUT: mixing with libraries that assume a context is still risky
// BEST PRACTICE: still use ConfigureAwait(false) in library code

Планирование в пуле потоков и контексте

Без контекста продолжения планируются с помощью TaskScheduler — обычно ThreadPoolTaskScheduler. При наличии контекста пользовательского интерфейса они передаются в цикл сообщений пользовательского интерфейса.

// Check current synchronization context:
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
    ?? "No context (thread pool)");

// In a WPF event handler: "DispatcherSynchronizationContext"
// In ASP.NET Core: null (no context)
// In a unit test with xUnit: "AsyncTestSyncContext"

// After ConfigureAwait(false):
await Task.Delay(1).ConfigureAwait(false);
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
    ?? "No context"); // null — context was abandoned

ConfigureAwait в циклах

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

// Library loop: ConfigureAwait(false) on every await
public async Task ProcessBatchAsync(IEnumerable<int> ids)
{
    foreach (var id in ids)
    {
        var item = await FetchItemAsync(id).ConfigureAwait(false);
        await SaveItemAsync(item).ConfigureAwait(false);
    }
}

ValueTask и ConfigureAwait

ValueTask также поддерживает ConfigureAwait(false). Применяйте его последовательно по тем же правилам, что и для Task.

public async ValueTask<string> GetFromCacheAsync(string key)
{
    // ValueTask with ConfigureAwait(false)
    var raw = await _store.GetAsync(key).ConfigureAwait(false);
    return raw ?? "";
}

Запуск задач через Run вне контекста

Используйте Task.Run, чтобы перенести вычислительную работу в пул потоков и гарантировать, что она никогда не будет выполняться в потоке, удерживающем контекст, например в потоке пользовательского интерфейса.

// WRONG in UI app: blocks UI thread
void ButtonClick(object? sender, EventArgs e)
{
    var result = HeavyComputation(); // blocks UI
    Label.Text = result;
}

// CORRECT: offload to thread pool
async void ButtonClick(object? sender, EventArgs e)
{
    var result = await Task.Run(() => HeavyComputation());
    // Continuation captured UI context: safe to update UI
    Label.Text = result;
}

Практический пример: библиотека HttpClient с ConfigureAwait

Рабочая обёртка HTTP-клиента повсеместно использует ConfigureAwait(false), поэтому её безопасно вызывать из любого контекста.

public class ApiClient
{
    private readonly HttpClient _http;
    private readonly JsonSerializerOptions _opts = new(JsonSerializerDefaults.Web);

    public ApiClient(HttpClient http) => _http = http;

    public async Task<T> GetAsync<T>(string path, CancellationToken ct = default)
    {
        using var response = await _http
            .GetAsync(path, ct)
            .ConfigureAwait(false);

        response.EnsureSuccessStatusCode();

        return await response.Content
            .ReadFromJsonAsync<T>(_opts, ct)
            .ConfigureAwait(false)
            ?? throw new InvalidOperationException("Null response");
    }
}

Быстрая проверка

Почему синхронное получение результата задачи в приложении WPF или WinForms часто приводит к взаимной блокировке?

Повторение: ConfigureAwait и SynchronizationContext

Основные выводы:

  • SynchronizationContext: планирует продолжения обратно в определённый поток, например в поток пользовательского интерфейса
  • ConfigureAwait(false): отказывается от захвата контекста и продолжает выполнение в пуле потоков
  • Код библиотеки: всегда используйте ConfigureAwait(false), чтобы предотвращать взаимные блокировки
  • Код приложения с пользовательским интерфейсом: используйте обычный await, чтобы вернуться в поток пользовательского интерфейса
  • ASP.NET Core: SynchronizationContext отсутствует, но для общих библиотек это всё равно хорошая практика
  • Никогда не блокируйте выполнение задачи Task с помощью .Result/.Wait() в приложениях, зависящих от контекста

Часто задаваемые вопросы

Урок «ConfigureAwait и контекст синхронизации» бесплатный?

Да — полный текст урока «ConfigureAwait и контекст синхронизации» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «ConfigureAwait и контекст синхронизации»?

Разберитесь в SynchronizationContext, используйте ConfigureAwait(false) в коде библиотек и избегайте взаимных блокировок в асинхронных потоках выполнения. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «ConfigureAwait и контекст синхронизации»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. IAsyncEnumerable и await foreach
  2. System.Threading.Channels
  3. ValueTask и предотвращение выделений памяти
  4. ConfigureAwait и контекст синхронизации
← Назад к C# Academy