//JorgenHoc
← Todos los artículos
Async C#Por Jorge CalderónActualizado 17 min read

ConfigureAwait(false) en C# — La Explicación Completa

Entiende exactamente qué hace ConfigureAwait(false) — medido en llamadas a SynchronizationContext.Post — cuándo lo necesitas, cuándo puedes omitirlo, y la sobrecarga ConfigureAwaitOptions de .NET 8.

#csharp#async#dotnet

Todo desarrollador de C# ha visto ConfigureAwait(false) disperso en el código de bibliotecas y se ha preguntado si debe copiar ese patrón. La respuesta corta depende enteramente del tipo de código que estás escribiendo y del entorno de ejecución que lo hospeda.

¿Qué es SynchronizationContext?

SynchronizationContext es una abstracción que permite que el runtime de .NET sepa dónde publicar una continuación después de que se complete un await. Piensa en él como un planificador que dice "cuando esta operación asíncrona termine, reanuda la ejecución aquí."

Distintos modelos de aplicación instalan distintos contextos:

Tipo de aplicación¿Tiene SynchronizationContext?Qué hace
WinFormsSí (WindowsFormsSynchronizationContext)Dirige continuaciones de vuelta al hilo de la UI
WPFSí (DispatcherSynchronizationContext)Igual — dirige al hilo del Dispatcher
ASP.NET (clásico, .NET Framework)Sí (AspNetSynchronizationContext)Garantiza que solo un hilo ejecute por solicitud
ASP.NET CoreNoSin contexto; se usan hilos del thread pool directamente
Aplicación de consola (.NET 5+)NoSin contexto; se usa el thread pool
Blazor WebAssemblyUn solo hilo; el contexto dirige de vuelta al hilo de JS
xUnit / NUnit / MSTestVaría según versiónAlgunos instalan un contexto para sincronización en pruebas

La presencia o ausencia de un SynchronizationContext es el dato clave que orienta cada decisión en este artículo.

ConfigureAwait(true) — El Comportamiento por Defecto

Cuando escribes un await sin más, C# lo compila como await someTask.ConfigureAwait(true). El argumento true significa:

  1. Antes de suspender, captura el SynchronizationContext actual (o TaskScheduler si no hay contexto).
  2. Cuando la tarea awaited completa, publica la continuación de vuelta a ese contexto capturado.

Esto es lo que hace seguro el código de UI — puedes hacer await de una llamada de red y luego tocar un TextBox en la siguiente línea sin un Dispatcher.Invoke explícito:

// Código detrás de WPF — ConfigureAwait(true) es el predeterminado
private async void Button_Click(object sender, RoutedEventArgs e)
{
    string result = await FetchDataAsync(); // se suspende aquí
    // Reanuda en el hilo de UI — seguro para tocar controles
    myLabel.Content = result;
}

El costo: después de que la tarea se complete en un hilo del thread pool, el runtime debe publicar la continuación de vuelta al contexto capturado (por ejemplo, el bucle de mensajes de la UI). Ese ida y vuelta tiene un costo, y en frameworks como ASP.NET clásico puede causar deadlocks.

ConfigureAwait(false) — Omitir la Recaptura del Contexto

ConfigureAwait(false) le dice al awaiter: "No necesito reanudar en el contexto original. Reanuda en el hilo que esté disponible (normalmente un hilo del thread pool)."

string result = await FetchDataAsync().ConfigureAwait(false);
// Reanuda en un hilo del thread pool — NO tocar controles de UI aquí

Internamente, la máquina de estados generada por el compilador verifica el indicador ContinueOnCapturedContext del awaiter. Cuando es false, la continuación se programa directamente en el thread pool (mediante ThreadPool.QueueUserWorkItem o similar) en lugar de publicarse al SynchronizationContext capturado.

Comparación Paso a Paso

// --- Flujo con ConfigureAwait(true) ---
// Hilo: hilo de UI (SynchronizationContext = DispatcherSynchronizationContext)
await Task.Delay(100);
// 1. Se captura DispatcherSynchronizationContext
// 2. Task.Delay completa en el hilo del thread pool T2
// 3. T2 publica la continuación en la cola del Dispatcher
// 4. El hilo de UI la recoge de la cola
// Hilo: hilo de UI nuevamente ✓
 
// --- Flujo con ConfigureAwait(false) ---
// Hilo: hilo de UI (SynchronizationContext = DispatcherSynchronizationContext)
await Task.Delay(100).ConfigureAwait(false);
// 1. El SynchronizationContext intencionalmente NO se captura
// 2. Task.Delay completa en el hilo del thread pool T2
// 3. La continuación se ejecuta directamente en T2 (sin ida y vuelta al Dispatcher)
// Hilo: hilo del thread pool T2

Medido: Contando los Posts

Esa descripción del flujo es comprobable, porque "publicar la continuación de vuelta" es una llamada a SynchronizationContext.Post. samples/configureawait-false-csharp instala un contexto de un solo hilo estilo UI con un contador en Post y ejecuta el mismo método de ambas formas — números deterministas, idénticos en cualquier máquina, a diferencia de los tiempos en nanosegundos:

Escenario (en el hilo del contexto)AwaitsPosts contadosTambién verificado
Awaits simples55el contexto sobrevive; sigue en el hilo estilo UI
Todos con ConfigureAwait(false)50Current es null tras el primer await; fuera del hilo de UI
ConfigureAwait(false) solo en el primer await1 + 4 simples0la regla de propagación de abajo
Sin contexto (hilo de consola)2n/aambas formas se comportan idéntico — el caso de ASP.NET Core

Un await, un Post; ConfigureAwait(false) omite exactamente eso. Todo lo demás en este artículo se deriva de esos números.

Salida de consola del sample: cinco awaits simples producen cinco Posts de vuelta al contexto y el SynchronizationContext sobrevive a cada await; los mismos cinco awaits con ConfigureAwait(false) producen cero Posts y Current pasa a ser null; ConfigureAwait(false) solo en el primer await sigue dando cero Posts en total; un hilo de consola sin contexto se comporta idéntico en ambas formas; y una excepción tras ConfigureAwait(false) se captura con normalidad. Todas las comprobaciones pasaron.
La ejecución del sample: cada afirmación verificada, terminando con el resumen de una línea — ConfigureAwait(false) hace una sola cosa: omite el Post.

El Deadlock Clásico — Por Qué Importa ConfigureAwait(false)

La razón más importante para conocer esta API es la prevención de deadlocks en patrones de bloqueo sobre código asíncrono. Considera ASP.NET clásico (.NET Framework) donde un SynchronizationContext limita la concurrencia a un hilo por solicitud:

// Método de biblioteca — NO usa ConfigureAwait(false)
public async Task<string> GetDataAsync()
{
    await Task.Delay(500); // captura AspNetSynchronizationContext
    return "hello";
}
 
// Acción del controlador — bloquea de forma síncrona (mala práctica, pero ocurre)
public ActionResult Index()
{
    // .Result bloquea el hilo de la solicitud mientras mantiene el bloqueo del contexto
    string data = GetDataAsync().Result; // DEADLOCK
    return Content(data);
}

Por qué ocurre el deadlock:

  1. Index() llama a GetDataAsync() y bloquea el hilo de solicitud con .Result.
  2. El hilo de solicitud retiene el AspNetSynchronizationContext.
  3. Task.Delay completa e intenta publicar la continuación de vuelta a ese contexto.
  4. El contexto está bloqueado (el hilo de solicitud está bloqueado en .Result).
  5. La continuación espera el contexto. El hilo bloqueado espera la continuación. Deadlock.

Solución con ConfigureAwait(false):

public async Task<string> GetDataAsync()
{
    // NO intenta volver al AspNetSynchronizationContext
    await Task.Delay(500).ConfigureAwait(false);
    return "hello"; // ejecuta en un hilo del thread pool — sin contexto necesario
}

Ahora cuando Task.Delay completa, la continuación se ejecuta en un hilo del thread pool en lugar de intentar re-entrar al contexto retenido. El deadlock se rompe.

⚠️

El deadlock solo ocurre cuando alguien llama .Result, .Wait(), o GetAwaiter().GetResult() en un método asíncrono que usa el ConfigureAwait(true) predeterminado. La solución real es hacer toda la cadena de llamadas asíncrona. ConfigureAwait(false) es una red de seguridad, no una licencia para bloquear.

Deadlock en WinForms / WPF — El Mismo Patrón

// WPF: manejador de clic que llama .Result (nunca hagas esto en código real)
private void BadButton_Click(object sender, RoutedEventArgs e)
{
    // El hilo de UI se bloquea, retiene DispatcherSynchronizationContext
    string result = SomeLibraryMethod().Result; // deadlock si la biblioteca usa ConfigureAwait(true)
    myLabel.Content = result;
}
 
// Biblioteca — correcto: usa ConfigureAwait(false) en todas partes
public async Task<string> SomeLibraryMethod()
{
    var data = await HttpClient.GetStringAsync("https://example.com")
        .ConfigureAwait(false); // no intentará publicar de vuelta al Dispatcher
    return data.ToUpper();
}

Cuándo ConfigureAwait(false) Es Irrelevante

ASP.NET Core

ASP.NET Core deliberadamente no tiene SynchronizationContext. Cuando no hay contexto que capturar, ConfigureAwait(true) y ConfigureAwait(false) se comportan de forma idéntica — ambos reanudan en un hilo del thread pool.

// Controlador de ASP.NET Core — ConfigureAwait(false) no tiene efecto aquí
[HttpGet("data")]
public async Task<IActionResult> GetData()
{
    // Comportamiento idéntico con o sin ConfigureAwait(false)
    var result = await _service.FetchAsync();
    return Ok(result);
}

Aplicaciones de Consola (.NET 5+)

Las aplicaciones de consola tampoco tienen SynchronizationContext. Cualquier await ya reanuda en el thread pool. Agregar ConfigureAwait(false) es una operación nula.

static async Task Main(string[] args)
{
    // No hay SynchronizationContext — ConfigureAwait(false) es redundante
    var data = await File.ReadAllTextAsync("input.txt").ConfigureAwait(false);
    Console.WriteLine(data);
}

Por Qué el Código de Biblioteca Siempre Debe Usar ConfigureAwait(false)

El autor de una biblioteca no puede saber quién llamará su código. El llamador podría ser:

  • Una aplicación WPF con DispatcherSynchronizationContext
  • Una aplicación ASP.NET clásica con AspNetSynchronizationContext
  • Una aplicación Blazor con su propio contexto
  • Un ejecutor de pruebas unitarias que instala un contexto personalizado

Si la biblioteca usa ConfigureAwait(true) (el predeterminado) y el llamador bloquea en .Result, el escenario de deadlock se vuelve posible. Si la biblioteca usa ConfigureAwait(false), se desuscribe completamente del contexto del llamador y no puede contribuir al deadlock.

// Buen código de biblioteca — agnóstico al contexto en todas partes
public class DataClient
{
    private readonly HttpClient _http;
 
    public DataClient(HttpClient http) => _http = http;
 
    public async Task<UserDto> GetUserAsync(int id)
    {
        // ConfigureAwait(false) en cada await — la biblioteca no posee el contexto
        var response = await _http.GetAsync($"/users/{id}").ConfigureAwait(false);
        response.EnsureSuccessStatusCode();
 
        var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        return JsonSerializer.Deserialize<UserDto>(json)!;
    }
 
    public async Task<IReadOnlyList<OrderDto>> GetOrdersAsync(int userId)
    {
        var response = await _http.GetAsync($"/users/{userId}/orders").ConfigureAwait(false);
        response.EnsureSuccessStatusCode();
 
        await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
        var orders = await JsonSerializer.DeserializeAsync<List<OrderDto>>(stream).ConfigureAwait(false);
        return orders ?? [];
    }
}
💡

La regla general: si estás escribiendo un paquete NuGet o una biblioteca compartida, agrega ConfigureAwait(false) a cada await. Si estás escribiendo código de aplicación apuntando a ASP.NET Core o una aplicación de consola, es opcional y agrega ruido visual sin ningún beneficio.

La Regla de Propagación de Contexto

Una vez que usas ConfigureAwait(false) en un solo await dentro de un método, todos los awaits siguientes en ese mismo método también pueden ejecutarse sin el contexto, independientemente de su propia configuración de ConfigureAwait. Esto se debe a que SynchronizationContext.Current se vuelve null en el hilo del thread pool donde continúa la ejecución.

public async Task ExampleAsync()
{
    // Aún en el contexto original
    await FirstOperation().ConfigureAwait(false);
 
    // Ahora en el hilo del thread pool — SynchronizationContext.Current es null
    // Este ConfigureAwait(true) es efectivamente igual a ConfigureAwait(false)
    // porque no hay contexto al que volver
    await SecondOperation().ConfigureAwait(true); // redundante — sin contexto presente
    await ThirdOperation(); // también está bien — sin contexto que capturar
}

Esto no significa que debas ser inconsistente. Escribe ConfigureAwait(false) en cada await del código de biblioteca para ser explícito y evitar confusiones.

El sample verifica esta regla por conteo: un ConfigureAwait(false) seguido de cuatro awaits simples produjo cero Posts — los awaits simples ya no tenían contexto que capturar.

Analizadores de Roslyn

Agregar manualmente ConfigureAwait(false) a cada await es tedioso y propenso a errores. Varios analizadores de Roslyn lo aplican automáticamente.

Microsoft.VisualStudio.Threading.Analyzers

Instala mediante NuGet:

dotnet add package Microsoft.VisualStudio.Threading.Analyzers

Este paquete proporciona la regla VSTHRD111 (UseConfigureAwait) que advierte sobre cualquier await que no tenga ConfigureAwait:

// Advertencia VSTHRD111: Usa .ConfigureAwait(false) al awaitar una tarea
var result = await SomeTaskAsync(); // ⚠ VSTHRD111

Configúralo en .editorconfig para tratarlo como error en proyectos de biblioteca:

[*.cs]
dotnet_diagnostic.VSTHRD111.severity = error

Roslynator

dotnet add package Roslynator.Analyzers

La regla RCS1090 (UseConfigureAwaitFalse) proporciona una aplicación similar.

Enfoque con .editorconfig

También puedes configurar el analizador integrado CA2007 (disponible en los analizadores del SDK de .NET):

[*.cs]
# CA2007: Considera llamar ConfigureAwait en la tarea awaited
dotnet_diagnostic.CA2007.severity = warning

Nota: CA2007 solo se activa al construir con <EnableNETAnalyzers>true</EnableNETAnalyzers>, que es el predeterminado para nuevos proyectos de estilo SDK que apuntan a .NET 5+.

.NET 8: ConfigureAwaitOptions

Primero, aclarando un mito que este mismo artículo solía repetir: no existe un valor predeterminado de ConfigureAwait(false) a nivel de proyecto en .NET — ni propiedad de MSBuild, ni RuntimeHostConfigurationOption, ni atributo a nivel de ensamblado. Una versión anterior de este artículo describía tal mecanismo; no existe — el ConfigureAwait FAQ de Stephen Toub explica directamente por qué el equipo ha rechazado repetidamente agregar un interruptor global. La única palanca a nivel de proyecto sigue siendo la aplicación con analizadores (abajo).

Lo que .NET 8 agregó es una sobrecarga: ConfigureAwait(ConfigureAwaitOptions), un enum de flags que generaliza el booleano:

// ConfigureAwaitOptions.None == ConfigureAwait(false)
await task.ConfigureAwait(ConfigureAwaitOptions.None);
 
// ConfigureAwaitOptions.ContinueOnCapturedContext == ConfigureAwait(true)
await task.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext);
 
// Nuevo: completa el await aunque la tarea haya fallado o sido cancelada — no
// relanza la excepción. Solo válido en Task no genérico (un Task<T> no tendría
// resultado que devolverte).
await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
 
// Nuevo: siempre cede el control, incluso si la tarea ya completó — el await nunca
// continúa sincrónicamente. Útil para forzar equidad o salir de una ruta con lock.
await task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
 
// Son flags, así que se combinan:
await task.ConfigureAwait(
    ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ForceYielding);

La sobrecarga existe en Task y Task<TResult> (no en ValueTask en .NET 8), y SuppressThrowing en un Task<TResult> lanza ArgumentOutOfRangeException en la llamada a ConfigureAwait — el mal uso falla rápido en lugar de devolver silenciosamente un resultado por defecto.

La Verdadera Solución a Nivel de Proyecto: Aplicarlo con Analizadores

Como no existe un predeterminado, el enfoque práctico para todo el repositorio es convertir la regla del analizador en error en Directory.Build.props:

<!-- Directory.Build.props — aplica a todos los proyectos en el árbol de directorio -->
<Project>
  <PropertyGroup>
    <!-- Tratar ConfigureAwait faltante como error de compilación en todo el repositorio -->
    <WarningsAsErrors>$(WarningsAsErrors);CA2007</WarningsAsErrors>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <AnalysisMode>All</AnalysisMode>
  </PropertyGroup>
</Project>

Conceptos Erróneos Comunes

Error 1: ConfigureAwait(false) Mejora el Rendimiento

La medición de arriba le pone forma precisa a esto: lo que ConfigureAwait(false) ahorra es un Post por await — y en ASP.NET Core o una app de consola, cero, porque no hay contexto al que publicar. Un post de contexto es barato; la razón para usar ConfigureAwait(false) es blindar el código de biblioteca contra deadlocks, no la velocidad. No lo agregues al código de aplicación por rendimiento — el ruido que agrega supera cualquier ganancia teórica.

// NO hagas esto por "rendimiento" en ASP.NET Core — agrega ruido sin beneficio
public async Task<string> GetDataAsync()
{
    return await _cache.GetAsync("key").ConfigureAwait(false); // inútil en ASP.NET Core
}

Error 2: ConfigureAwait(false) Afecta el Manejo de Errores

La propagación de excepciones funciona de forma idéntica independientemente de ConfigureAwait. Las excepciones lanzadas dentro de una tarea awaited siguen siendo capturadas y relanzadas en el punto await, independientemente del hilo en que se ejecute esa continuación. (La última comprobación del sample lo verifica: un método que lanza después de un await con ConfigureAwait(false) se captura con un try/catch ordinario en el punto de llamada. La única excepción a la regla en .NET 8 es opcional: ConfigureAwaitOptions.SuppressThrowing, cubierta arriba.)

public async Task DemonstrateExceptionAsync()
{
    try
    {
        // La excepción lanzada dentro de FetchAsync se relanza aquí,
        // independientemente de ConfigureAwait
        await FetchAsync().ConfigureAwait(false);
    }
    catch (HttpRequestException ex)
    {
        // Sigue siendo capturada — ConfigureAwait no afecta el flujo de excepciones
        _logger.LogError(ex, "Fetch failed");
        throw;
    }
}

Error 3: ConfigureAwait(false) Hace el Código Thread-Safe

ConfigureAwait(false) solo cambia en qué hilo se ejecuta la continuación. No tiene efecto sobre la seguridad de hilos, el estado compartido, o las condiciones de carrera. Aún necesitas primitivas de sincronización adecuadas al acceder a estado mutable compartido.

Error 4: Necesitas ConfigureAwait(false) en Métodos async void

Los métodos async void son manejadores de eventos y escenarios de disparar-y-olvidar. Siguen capturando el SynchronizationContext y aún se benefician de ConfigureAwait(false) si llaman código de biblioteca que podría bloquearse. Pero aplican las mismas reglas — solo lo necesitas para prevenir deadlocks o evitar cambios de contexto innecesarios en frameworks que tienen un contexto.

Patrones Prácticos

Patrón 1: Biblioteca Envolvente de HttpClient

public class WeatherApiClient
{
    private readonly HttpClient _http;
    private readonly ILogger<WeatherApiClient> _logger;
 
    public WeatherApiClient(HttpClient http, ILogger<WeatherApiClient> logger)
    {
        _http = http;
        _logger = logger;
    }
 
    public async Task<WeatherForecast?> GetForecastAsync(
        string city,
        CancellationToken cancellationToken = default)
    {
        // Código de biblioteca: ConfigureAwait(false) en cada await
        var url = $"/api/weather?city={Uri.EscapeDataString(city)}";
 
        using var response = await _http
            .GetAsync(url, cancellationToken)
            .ConfigureAwait(false);
 
        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("Weather API returned {StatusCode}", response.StatusCode);
            return null;
        }
 
        await using var stream = await response.Content
            .ReadAsStreamAsync(cancellationToken)
            .ConfigureAwait(false);
 
        return await JsonSerializer
            .DeserializeAsync<WeatherForecast>(stream, cancellationToken: cancellationToken)
            .ConfigureAwait(false);
    }
}

Patrón 2: ViewModel de WPF — Dónde NO Usar ConfigureAwait(false)

public class MainViewModel : INotifyPropertyChanged
{
    private string _status = string.Empty;
    public string Status
    {
        get => _status;
        set { _status = value; OnPropertyChanged(); }
    }
 
    public async Task LoadDataAsync()
    {
        // Await simple, deliberadamente: este método establece estado enlazado a la UI
        // después, así que debe volver al hilo de UI. Los ConfigureAwait(false) van
        // DENTRO de _apiClient (código de biblioteca) — la frontera entre "I/O sin
        // contexto" y "actualización de UI con contexto" es la frontera de la API,
        // no una línea de este método.
        var data = await _apiClient.GetForecastAsync("London");
 
        // Reanudado en el hilo de UI — seguro tocar estado enlazado
        Status = data?.Summary ?? "Sin datos";
    }
}
⚠️

Un patrón que encontrarás por ahí (y en una versión anterior de este artículo): llamar a TaskScheduler.FromCurrentSynchronizationContext() después de un await ... ConfigureAwait(false) para "volver" a la UI. Eso lanza InvalidOperationException — después de ConfigureAwait(false) no hay SynchronizationContext actual que capturar (el demo 2 del sample verifica exactamente esto: Current es null). Si un método necesita el hilo de UI después de sus awaits, usa await simple en ese método y empuja ConfigureAwait(false) hacia la biblioteca que llama.

Patrón 3: Repositorio de Entity Framework Core

public class UserRepository
{
    private readonly AppDbContext _context;
 
    public UserRepository(AppDbContext context) => _context = context;
 
    public async Task<User?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        // EF Core soporta ConfigureAwait(false) — seguro en código de biblioteca
        return await _context.Users
            .AsNoTracking()
            .FirstOrDefaultAsync(u => u.Id == id, ct)
            .ConfigureAwait(false);
    }
 
    public async Task<int> CreateUserAsync(User user, CancellationToken ct = default)
    {
        _context.Users.Add(user);
        await _context.SaveChangesAsync(ct).ConfigureAwait(false);
        return user.Id;
    }
}

Tabla de Decisión

Escenario¿Usar ConfigureAwait(false)?Razón
Paquete NuGet / biblioteca de clases compartidaSí — siempreEl contexto del llamador es desconocido; previene deadlocks
Controlador/servicio de ASP.NET CoreNo (opcional)No hay SynchronizationContext presente
Aplicación de consola (.NET 5+)No (opcional)No hay SynchronizationContext presente
WPF / WinForms — capa de UIDependeUsa false para trabajo de I/O; omite al actualizar estado de UI
WPF / WinForms — I/O en ViewModelEl I/O no debe retener el contexto de UI
ASP.NET clásico (.NET Framework)Sí — siempreRiesgo de deadlock con AspNetSynchronizationContext
Blazor WebAssemblyUn solo hilo; evita sobrecarga de contexto
Pruebas unitarias (cualquier ejecutor)No (opcional)Varía; generalmente seguro sin él en .NET 5+
Manejadores de eventos async voidNoDeben reanudar en el hilo de UI para actualizar controles

Resumen

ConfigureAwait(false) hace una sola cosa: le dice al awaiter que no capture ni restaure el SynchronizationContext actual. Las consecuencias son:

  • Las continuaciones se ejecutan en el thread pool en lugar de publicarse de vuelta al contexto original.
  • Los deadlocks se previenen en frameworks que tienen un contexto y donde alguien está bloqueando en la tarea con .Result o .Wait().
  • El impacto en el rendimiento es insignificante — no lo uses como optimización.
  • El manejo de errores no se ve afectado — las excepciones se propagan exactamente de la misma manera.

La regla práctica es simple: el código de biblioteca usa ConfigureAwait(false) en cada await; el código de aplicación ASP.NET Core y de consola no lo necesita. Usa los analizadores de Roslyn CA2007 o VSTHRD111 para aplicar la regla automáticamente en toda una base de código.

Lecturas adicionales

Sobre el autor

Jorge Calderón

Ingeniero de software con más de una década construyendo y operando aplicaciones .NET en producción — capas de datos con EF Core, servicios intensivos en async y despliegues en Azure y contenedores. Cada benchmark y proyecto de ejemplo de estas guías está publicado en un repositorio público de GitHub para que puedas reproducirlo.

Perfil de GitHubLinkedIn ↗Benchmarks y código de ejemplo

Artículos relacionados