Хорошо ли использовать async void для запуска задачи, не требующей ресурсов процессора, после определенной задержки?

#c# #asynchronous

#c# #асинхронный

Вопрос:

Вот мой код :

 class StateManager
{
    private readonly ConcurrentDictionary<int, bool> _states = new ConcurrentDictionary<int, bool>();

    public void SetState(int id, bool state)
    {
        _states[id] = state;

        if (!state)
            RemoveLately(id);
    }

    private async void RemoveLately(int id)
    {
        await Task.Delay(10000).ConfigureAwait(false);
        _states.TryRemove(id, out _);

    }
}
  

Моя цель — удалить элемент по истечении определенного промежутка времени. Я не хочу использовать Task.Выполнить для RemoveLately , поскольку это может быть вызвано тысячи раз.
Каковы могут быть недостатки такой практики, если таковые имеются?

Комментарии:

1. Измените void на Task .

2. async void предназначен только для обработчиков событий. RemoveLately не является обработчиком событий. Недостатки велики и хорошо документированы. async void методы нельзя ожидать или отслеживать. Если StateManager удален, метод все равно будет запущен и попытается вызвать _states.TryRemove()

Ответ №1:

Моя цель — удалить элемент по истечении определенного промежутка времени.

Тогда почему бы не использовать кеш?

Хорошо ли использовать async void?

Руководство для async void заключается в том, чтобы избегать async void , если вы не реализуете обработчик событий (или что-то логически похожее на обработчик событий). Итак, реальный вопрос здесь таков: RemoveLately логически ли это «обработчик событий»? Я вижу аргумент, в котором его можно было бы считать таковым; в частности, TryRemove вызывается в ответ на «событие» таймера ( Task.Delay ). Так что я бы не стал абсолютно утверждать, что async void здесь неправильно, но у него есть недостатки.

Каковы могут быть недостатки такой практики, если таковые имеются?

Существует одна основная проблема с async void методами: другой код не может знать, когда этот метод завершен.

Эта основная проблема проявляется несколькими способами:

  • Ваш код не может перехватывать или обрабатывать исключения из RemoveLately . Поскольку нет способа наблюдать завершение для async void методов, также нет способа наблюдать исключения. Таким образом, async void методы просто вызывают любые исключения непосредственно в их оригинале SynchronizationContext . В большинстве случаев это означает, что исключения в async void методах приведут к аварийному завершению работы приложения.
  • async void методы сложно тестировать. Это потому, что код модульного тестирования не может знать, когда async void метод завершен.
  • Ваш код не может знать, когда безопасно завершить работу (где область действия «завершить работу» может означать «выйти из программы», или «утилизировать StateManager «, или что-либо промежуточное). Это потому, что ваш код не может знать, может ли async void еще выполняться работа. В данном конкретном случае, когда RemoveLately просто удаляется объект из кэша, это должно быть нормально игнорировать, но в общем случае async void означает, что приложение никогда не узнает, когда это «сделано».

Ответ №2:

Отсутствие async void (почти) всегда плохо (так это). Как указано Стивеном в его статье и его ответе здесь, есть несколько причин для использования методов async void (а именно async Eventhandlers, которые никогда не имеют никакого другого «возвращаемого» значения, кроме void). Причины, по которым вам следует перейти на асинхронную задачу, и почему они лучше, объяснены Стивеном в его ответе.

Вот почему я бы предложил, чтобы ваш метод выглядел следующим образом:

 private async Task RemoveLately(int id)
    {
        await Task.Delay(10000).ConfigureAwait(false);
        _states.TryRemove(id, out _);

    }
  

В качестве небольшого замечания (если можно): Если вы не уверены, что выбрать, и / или вы можете выбрать либо Task (или Task<T> ), либо async void , то попробуйте использовать Task , потому что почти во всех случаях (кроме обработки событий) вы лучше справляетесь с Task , чем с async void .