0Pricing
C# Academy · Lekcja

ConfigureAwait i kontekst synchronizacji

Poznaj SynchronizationContext, używaj ConfigureAwait(false) w kodzie bibliotek i unikaj deadlocków w przepływach asynchronicznych.

ConfigureAwait i kontekst synchronizacji to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Czym jest SynchronizationContext?

SynchronizationContext kontroluje sposób planowania kodu kontynuacji po wykonaniu await. W aplikacjach interfejsu użytkownika (WPF, WinForms) zapewnia, że aktualizacje interfejsu są wykonywane w wątku interfejsu użytkownika. W ASP.NET Core nie ma kontekstu synchronizacji (został usunięty). Zrozumienie tego mechanizmu jest kluczowe dla pisania poprawnego kodu asynchronicznego.

Jak działa ConfigureAwait

.ConfigureAwait(false) informuje awaiter, aby NIE przechwytywał bieżącego obiektu SynchronizationContext ani nie wznawiał na nim wykonania. Kontynuacja jest wykonywana zamiast tego na dowolnym dostępnym wątku z puli wątków.

// 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

Klasyczny wzorzec zakleszczenia

Blokowanie obiektu Task za pomocą .Result lub .Wait() przy aktywnym kontekście synchronizacji powoduje klasyczne zakleszczenie — kontynuacja czeka na kontekst, który jest zablokowany, czekając na tę kontynuację.

// 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) w bibliotekach

Kod biblioteki niezależny od kontekstu powinien zawsze używać ConfigureAwait(false) przy każdym await. Zapobiega to zakleszczeniom, gdy wywołujący korzystają z biblioteki w środowisku zależnym od kontekstu.

// 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: brak SynchronizationContext

ASP.NET Core celowo nie udostępnia obiektu SynchronizationContext. Kontynuacje są wykonywane w puli wątków. ConfigureAwait(false) jest technicznie operacją bez efektu, ale nadal warto go stosować w kodzie współdzielonym z innymi frameworkami.

// 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

Planowanie przez pulę wątków a planowanie przez kontekst

Bez kontekstu kontynuacje są planowane przez obiekt TaskScheduler — zazwyczaj przez ThreadPoolTaskScheduler. W przypadku kontekstu interfejsu użytkownika są przekazywane do pętli komunikatów interfejsu użytkownika.

// 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 w pętlach

Podczas oczekiwania w pętli kontynuacja każdej iteracji jest wykonywana w przechwyconym kontekście, chyba że użyto ConfigureAwait(false). W kodzie biblioteki należy stosować go konsekwentnie przy każdym await w pętli.

// 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 i ConfigureAwait

Obiekt ValueTask również obsługuje ConfigureAwait(false). Należy stosować go konsekwentnie, zgodnie z tymi samymi zasadami co w przypadku Task.

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

Task.Run jako sposób na uniknięcie kontekstu

Należy używać Task.Run, aby przenieść pracę intensywnie wykorzystującą procesor do puli wątków i zagwarantować, że nie będzie ona wykonywana na wątku związanym z kontekstem, takim jak wątek interfejsu użytkownika.

// 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;
}

Praktyczny przykład: biblioteka HttpClient z ConfigureAwait

Produkcyjny wrapper klienta HTTP konsekwentnie używa ConfigureAwait(false), dzięki czemu jest bezpieczny podczas wywoływania z dowolnego kontekstu.

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");
    }
}

Szybki test

Dlaczego wywołanie .Result na obiekcie Task w aplikacji WPF lub WinForms często powoduje zakleszczenie?

Podsumowanie: ConfigureAwait i SynchronizationContext

Najważniejsze informacje:

  • SynchronizationContext: planuje kontynuacje z powrotem na określonym wątku, na przykład wątku interfejsu użytkownika
  • ConfigureAwait(false): rezygnuje z przechwytywania kontekstu i kontynuuje pracę w puli wątków
  • Kod biblioteki: zawsze należy używać ConfigureAwait(false), aby zapobiegać zakleszczeniom
  • Kod aplikacji z interfejsem użytkownika: należy używać domyślnego await, aby powrócić na wątek interfejsu użytkownika
  • ASP.NET Core: brak SynchronizationContext — mimo to w bibliotekach współdzielonych nadal warto stosować tę praktykę
  • W aplikacjach zależnych od kontekstu nigdy nie należy blokować obiektu Task za pomocą .Result/.Wait()

Często zadawane pytania

Czy lekcja „ConfigureAwait i kontekst synchronizacji” jest bezpłatna?

Tak — pełny tekst „ConfigureAwait i kontekst synchronizacji” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „ConfigureAwait i kontekst synchronizacji”?

Poznaj SynchronizationContext, używaj ConfigureAwait(false) w kodzie bibliotek i unikaj deadlocków w przepływach asynchronicznych. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „ConfigureAwait i kontekst synchronizacji”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. IAsyncEnumerable i await foreach
  2. System.Threading.Channels
  3. ValueTask i unikanie alokacji
  4. ConfigureAwait i kontekst synchronizacji
← Powrót do C# Academy