Подтвердить что ты не робот

Fire-and-forget с async против "old async delegate"

Я пытаюсь заменить свои старые вызовы с использованием огня и забывания новым синтаксисом, надеясь на более простоту и, похоже, ускользает от меня. Вот пример

class Program
{
    static void DoIt(string entry) 
    { 
        Console.WriteLine("Message: " + entry);
    }

    static async void DoIt2(string entry)
    {
        await Task.Yield();
        Console.WriteLine("Message2: " + entry);
    }

    static void Main(string[] args)
    {
        // old way
        Action<string> async = DoIt;
        async.BeginInvoke("Test", ar => { async.EndInvoke(ar); ar.AsyncWaitHandle.Close(); }, null);
        Console.WriteLine("old-way main thread invoker finished");
        // new way
        DoIt2("Test2");   
        Console.WriteLine("new-way main thread invoker finished");
        Console.ReadLine();
    }
}

Оба подхода делают одно и то же, однако то, что я, похоже, получил (нет необходимости в EndInvoke и закрывать дескриптор, что является imho все еще немного спорным). Я теряю по-новому, ожидая Task.Yield(), что фактически создает новую проблему переписывания всех существующих методов асинхронного F & F только для добавления этого однострочного. Есть ли невидимая прибыль с точки зрения производительности/очистки?

Как я могу применить async, если не могу изменить метод фона? Мне кажется, что нет прямого пути, мне нужно будет создать метод асинхронной обертки, который будет ждать Task.Run()?

Изменить: теперь я вижу, что у меня могут возникнуть вопросы. Возникает вопрос: учитывая синхронный метод A(), как я могу называть его асинхронно с помощью async/await в режиме "огонь-и-забыть", не получая решения, которые сложнее, чем "старый способ"

4b9b3361

Ответ 1

Избегайте async void. Он имеет сложную семантику вокруг обработки ошибок; Я знаю, что некоторые люди называют это "огнем и забывают", но я обычно использую фразу "огонь и авария".

Возникает вопрос: учитывая синхронный метод A(), как я могу назвать его асинхронно, используя async/await в режиме "огонь и забухание", не получая решения, которые сложнее, чем "старый способ"

Вам не нужно async/await. Просто назовите его так:

Task.Run(A);

Ответ 2

Как отмечено в других ответах, и в этом превосходном блоге вы хотите избежать использования async void вне обработчиков событий пользовательского интерфейса. Если вы хотите использовать безопасный метод "TG41", "подумайте", рассмотрите возможность использования этого шаблона (кредит @ReedCopsey; этот метод он дал мне в чате):

  1. Создайте метод расширения для Task. Он запускает пройденный Task и перехватывает/регистрирует любые исключения:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. При их создании всегда используйте методы в стиле Task, а не в async void.

  3. Вызовите эти методы следующим образом:

    MyTaskAsyncMethod().FireAndForget();
    

Вам не нужно await это (и при этом оно не будет генерировать предупреждение await). Он также будет корректно обрабатывать любые ошибки, и, поскольку это единственное место, которое вы когда-либо ставили async void, вам не нужно забывать ставить блоки try/catch везде.

Это также дает вам возможность не использовать метод async в качестве метода "забей и забудь", если вы действительно хотите await использовать его как обычно.

Ответ 3

Мне кажется, что "ожидание" чего-то и "огонь и забвение" - это две ортогональные концепции. Вы либо запускаете метод асинхронно и не заботитесь о результатах, либо хотите возобновить выполнение в исходном контексте после завершения операции (и, возможно, использовать возвращаемое значение), что и есть то, что ждет. Если вы просто хотите выполнить метод в потоке ThreadPool (чтобы ваш пользовательский интерфейс не блокировался), перейдите к

Task.Factory.StartNew(() => DoIt2("Test2"))

и все будет в порядке.

Ответ 4

Я считаю, что эти методы "огонь и забухание" были в основном артефактами, нуждающимися в чистом способе чередования UI и фонового кода, чтобы вы могли написать свою логику как последовательность последовательных инструкций. Поскольку async/await заботится о сортировке через SynchronizationContext, это становится проблемой. Встроенный код в более длинной последовательности эффективно становится вашим блоком "огонь и заб", который ранее был запущен из подпрограммы в фоновом потоке. Это эффективно инверсия шаблона.

Основное отличие состоит в том, что блоки между ожиданиями более похожи на Invoke, чем BeginInvoke. Если вам нужно больше походить на BeginInvoke, вы можете вызвать следующий асинхронный метод (возвращая задачу), а затем не ожидайте возвращенной задачи до тех пор, пока код, который вы хотите "BeginInvoke".

    public async void Method()
    {
        //Do UI stuff
        await SomeTaskAsync();
        //Do more UI stuff (as if called via Invoke from a thread)
        var nextTask = NextTaskAsync();
        //Do UI stuff while task is running (as if called via BeginInvoke from a thread)
        await nextTask;
    }