C# Academy · Lección

ConfigureAwait y contexto de sincronización

Comprenda SynchronizationContext, use ConfigureAwait(false) en código de bibliotecas y evite interbloqueos en flujos asíncronos.

Lección 4 de 412 pasos

ConfigureAwait y contexto de sincronización es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.

¿Qué es SynchronizationContext?

SynchronizationContext controla cómo se programa el código de continuación después de un await. En las aplicaciones de interfaz de usuario (WPF, WinForms), garantiza que las actualizaciones de la interfaz se realicen en el subproceso de la interfaz. En ASP.NET Core no existe un contexto de sincronización (se eliminó). Comprenderlo es fundamental para escribir código asíncrono correcto.

Cómo funciona ConfigureAwait

.ConfigureAwait(false) indica al awaiter que NO capture el SynchronizationContext actual ni reanude la ejecución en él. En su lugar, la continuación se ejecuta en cualquier subproceso disponible del grupo de subprocesos.

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

El patrón clásico de interbloqueo

Bloquear una Task con .Result o .Wait() mientras se mantiene un contexto de sincronización provoca un interbloqueo clásico: la continuación espera al contexto que está bloqueado esperando a la continuación.

// 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) en bibliotecas

El código de biblioteca que no depende del contexto debe usar siempre ConfigureAwait(false) en cada await. Esto evita interbloqueos cuando los consumidores usan la biblioteca desde un entorno sensible al contexto.

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

ASP.NET Core no tiene deliberadamente ningún SynchronizationContext. Las continuaciones se ejecutan en el grupo de subprocesos. ConfigureAwait(false) técnicamente no tiene efecto, pero sigue siendo una buena práctica para el código compartido con otros frameworks.

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

Grupo de subprocesos frente a programación mediante contexto

Sin un contexto, las continuaciones se programan mediante TaskScheduler, normalmente ThreadPoolTaskScheduler. Con un contexto de UI, se envían al bucle de mensajes de la UI.

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

Al usar await en un bucle, la continuación de cada iteración se ejecuta en el contexto capturado, a menos que se use ConfigureAwait(false). En el código de biblioteca, aplíquelo de forma coherente en cada await del bucle.

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

ValueTask también admite ConfigureAwait(false). Aplíquelo de forma coherente siguiendo las mismas reglas que para Task.

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

Task.Run para evitar el contexto

Use Task.Run para descargar el trabajo limitado por CPU al grupo de subprocesos y garantizar que nunca se ejecute en un subproceso que mantenga un contexto, como el subproceso de la UI.

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

Caso real: biblioteca HttpClient con ConfigureAwait

Un wrapper de cliente HTTP de producción usa ConfigureAwait(false) en todo el código para funcionar de forma segura al llamarse desde cualquier contexto.

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

Comprobación rápida

¿Por qué llamar a .Result sobre una Task en una aplicación WPF o WinForms suele provocar un interbloqueo?

Resumen: ConfigureAwait y SynchronizationContext

Conclusiones clave:

  • SynchronizationContext: programa las continuaciones para que vuelvan a un subproceso específico, como el subproceso de la UI
  • ConfigureAwait(false): abandona la captura del contexto y continúa en el grupo de subprocesos
  • Código de biblioteca: use siempre ConfigureAwait(false) para evitar interbloqueos
  • Código de aplicación con UI: use el await predeterminado para volver al subproceso de la UI
  • ASP.NET Core: no tiene SynchronizationContext, pero sigue siendo una buena práctica usarlo en bibliotecas compartidas
  • Nunca bloquee una Task con .Result/.Wait() en aplicaciones sensibles al contexto
Gratis para empezar

Aprende C# con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
93
Lecciones
346

Preguntas frecuentes

¿La lección «ConfigureAwait y contexto de sincronización» es gratis?

Sí — el texto completo de «ConfigureAwait y contexto de sincronización» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.

¿Qué aprenderé en «ConfigureAwait y contexto de sincronización»?

Comprenda SynchronizationContext, use ConfigureAwait(false) en código de bibliotecas y evite interbloqueos en flujos asíncronos. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C# Academy?

No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «ConfigureAwait y contexto de sincronización»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C# Academy?

Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. IAsyncEnumerable y await foreach
  2. System.Threading.Channels
  3. ValueTask y cómo evitar asignaciones
  4. ConfigureAwait y contexto de sincronización
← Volver a C# Academy