← Назад к вопросам

Почему нельзя использовать async void методы? Когда это допустимо?

1.8 Middle🔥 171 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проблемы async void методов

async void — это специальная конструкция в C#, которая позволяет создавать асинхронные методы, не возвращающие значение. Однако её использование крайне ограничено из-за серьёзных проблем.

Основные проблемы async void

1. Невозможность ожидания завершения

async void DoSomethingAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Completed");
}

// НЕЛЬЗЯ ожидать такой метод
// await DoSomethingAsync(); // Ошибка компиляции!

2. Необработанные исключения приводят к аварийному завершению

Когда исключение возникает в async void методе, оно не может быть перехвачено через механизм await и попадает в контекст синхронизации:

async void DangerousMethod()
{
    throw new InvalidOperationException("Катастрофа!");
}

try
{
    DangerousMethod(); // Исключение НЕ будет поймано здесь
}
catch (Exception) // Этот блок НЕ сработает
{
    // Код никогда не выполнится
}

3. Сложности с тестированием

Тестирование async void методов крайне затруднено, так как невозможно дождаться их завершения стандартными средствами:

[TestMethod]
public async Task TestAsyncVoid_Проблематично()
{
    // Нельзя корректно протестировать
    SomeAsyncVoidMethod(); // Как проверить завершение?
}

4. Нарушение композиции задач

async void методы не могут участвовать в композиции задач через Task.WhenAll, Task.WhenAny:

async void Method1() => await Task.Delay(100);
async void Method2() => await Task.Delay(200);

// Невозможно дождаться завершения всех методов
// await Task.WhenAll(Method1(), Method2()); // Ошибка!

5. Проблемы с отслеживанием выполнения

async void FireAndForget()
{
    // Если произойдёт ошибка, мы можем никогда о ней не узнать
    await ExternalServiceCallAsync();
}

Когда использование async void допустимо

1. Обработчики событий (Event Handlers)

Это ЕДИНСТВЕННЫЙ рекомендованный случай использования:

// UI события (WinForms, WPF, MAUI)
private async void Button_Click(object sender, EventArgs e)
{
    try
    {
        await LoadDataAsync();
        UpdateUI();
    }
    catch (Exception ex)
    {
        ShowErrorMessage(ex);
    }
}

// ASP.NET Core минимальные API (с версии 7.0)
app.MapGet("/data", async (HttpContext context) =>
{
    // Здесь используется async void под капотом
    await context.Response.WriteAsync("Hello World");
});

2. Методы жизненного цикла в UI фреймворках

// Xamarin.Forms / MAUI
protected override async void OnAppearing()
{
    base.OnAppearing();
    await InitializeAsync();
}

// ASP.NET Core Middleware (в некоторых сценариях)
public async void Invoke(HttpContext context)
{
    await _next(context);
}

3. Сценарии "запустил и забыл" с обработкой ошибок

public static class AsyncVoidHelper
{
    public static void SafeFireAndForget(
        Func<Task> taskFactory, 
        Action<Exception> onError = null)
    {
        async void WrappedAsync()
        {
            try
            {
                await taskFactory();
            }
            catch (Exception ex)
            {
                onError?.Invoke(ex);
                // Или логирование
                Logger.LogError(ex, "Async void error");
            }
        }
        
        WrappedAsync();
    }
}

// Использование
AsyncVoidHelper.SafeFireAndForget(
    async () => await ProcessDataAsync(),
    ex => Console.WriteLine($"Ошибка: {ex.Message}")
);

Рекомендации и лучшие практики

Всегда предпочитайте async Task вместо async void:

// ❌ ПЛОХО
async void BadMethod() { /* ... */ }

// ✅ ХОРОШО
async Task GoodMethodAsync() { /* ... */ }

// ✅ Ещё лучше с CancellationToken
async Task BestMethodAsync(CancellationToken cancellationToken = default)
{
    await SomeOperationAsync(cancellationToken);
}

Для обработчиков событий используйте защитный код:

private async void EventHandler(object sender, EventArgs e)
{
    // Защита от повторного входа
    var button = sender as Button;
    if (button != null) 
        button.Enabled = false;
    
    try
    {
        await PerformAsyncOperation();
    }
    finally
    {
        if (button != null) 
            button.Enabled = true;
    }
}

В ASP.NET Core используйте async Task для контроллеров:

public class ApiController : ControllerBase
{
    // ✅ Правильно
    [HttpGet]
    public async Task<IActionResult> GetData()
    {
        var data = await _service.GetDataAsync();
        return Ok(data);
    }
    
    // ❌ Неправильно (если это не Middleware)
    public async void BadAction()
    {
        // Потенциальные проблемы
    }
}

Вывод

async void методы нарушают фундаментальные принципы асинхронного программирования в C#. Их использование оправдано только в обработчиках событий, где сигнатура метода диктуется фреймворком. Во всех остальных случаях используйте async Task, который предоставляет корректную обработку исключений, возможность ожидания и композиции задач, что делает код более надёжным, тестируемым и поддерживаемым. Современные анализеры кода (например, Roslyn Analyzers) обычно предупреждают о нежелательном использовании async void, что помогает избежать потенциальных ошибок на ранних этапах разработки.

Почему нельзя использовать async void методы? Когда это допустимо? | PrepBro