C # async / await await не ждет - PullRequest
       9

C # async / await await не ждет

0 голосов
/ 17 октября 2019

Я использую .Net 4.7.2 и C # 7

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

private void ExecuteSequential()
{
    SomeMethod1();
    SomeMethod2();
    ExecuteParallelAsync();
    SomeMethod3();
    SomeMethod4();
}


private async void ExecuteParallelAsync()
{
    List<Action> methods = new List<Action>()
    {
        MyMethod1,
        MyMethod2,
        MyMethod3
    };


    await Task.Run( () => { Parallel.ForEach( methods , ( currentMethod ) => currentMethod.Invoke() ); } );            
}

До того, как SomeMethod3 будет выполнено ExecuteParallelAsync должно быть полностью завершено. Потому что это не так, очевидно, что-то не так с моим использованием асинхронных и ожидающих.

Что я делаю не так?

Заранее спасибо!

Ответы [ 5 ]

11 голосов
/ 17 октября 2019

async void - все ставки выключены;на самом деле, вы никогда не должны писать метод async void, если только вы не используете крошечный крошечный набор крайних угловых случаев, которые требуют его, таких как асинхронные обработчики событий.

До того, как выполнится SomeMethod3, ExecuteParallelAsync должен быть полностью завершен.

как он мог это знать? это: async void, что означает, что не дает вызывающей стороне ничего, что указывает состояние .

Для сделать это , это должно быть:

private async Task ExecuteParallelAsync() // or ValueTask

и вы должны ждать его на сайте вызова

await ExecuteParallelAsync();
3 голосов
/ 17 октября 2019

Ответ Марка - правильное решение, но я хотел объяснить больше о том, почему.

Очень важно понять методы async:

  1. Они начинаютсясинхронно.
  2. Когда используется ключевое слово await и await при неполном Task, , метод возвращает - да, даже если вы только на полпути через свой метод,Магия заключается в , что возвращает.

Итак, давайте посмотрим, что делает ваш код:

  1. Позвоните ExecuteParallelAsync().
  2. ExecuteParallelAsync() начинает работать синхронно.
  3. Task.Run возвращает объект Task, который еще не завершен.
  4. Ключевое слово await проверяет Task, видит, что ононе завершен, поэтому он создает новый Task, регистрирует оставшуюся часть метода как продолжение этого Task (в этом случае ничего не остается делать, но это все равно происходит) и возвращает .
  5. Поскольку тип возвращаемого значения void, он не может вернуть Task. Но он все равно возвращается!

Так что все это означает, что ExecuteParallelAsync() возвращается до того, как Task.Run завершено, и ExecuteSequential() не может определить, когда работа фактически завершена.

Это обычно называется «уволить и забыть» (то есть «иди делай это, и мне все равно, когда ты закончишь»).

Как указывает ответ Марка, если вы хотите знать, когдавсе готово, вам нужно вернуть Task, который вы затем можете await, чтобы узнать, когда это будет сделано.

Microsoft имеет очень хорошо написанные статьи об асинхронном программировании, которые вы можете прочитать, прочитав: Асинхронное программирование с асинхронным ожиданием

1 голос
/ 17 октября 2019

Если вы хотите реализацию без ключевого слова async / await sugar:

private void ExecuteParallelAsync()
{
    List<Action> methods = new List<Action>()
    {
        MyMethod1,
        MyMethod2,
        MyMethod3
    };

    Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray())   
}
0 голосов
/ 21 октября 2019

небольшой завершенный пример:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            SomeMethod1();
            ExecuteParallelAsync();
            SomeMethod2();
            Console.ReadKey();
        }

        static void SomeMethod1()
        {
            Console.WriteLine("SomeMethod1");
        }
        static void SomeMethod2()
        {
            Console.WriteLine("SomeMethod2");
        }

        static void ExecuteParallelAsync()
        {
            Console.WriteLine("\tstart of ExecuteParallelAsync");
            var rd = new Random();

            List<Action> methods = new List<Action>()
            {
                ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod1"); },
                ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod2"); },
                ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod3"); },
            };
            Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray());

            Console.WriteLine("\tend of ExecuteParallelAsync");
        }

    }
}

пример результата:

SomeMethod1
        start of ExecuteParallelAsync
MyMethod2
MyMethod3
MyMethod1
        end of ExecuteParallelAsync
SomeMethod2
0 голосов
/ 17 октября 2019

Шаг 1, воспроизведите

public void Method()
    {
        SomeMethod();
        SomeMethod();
        ExecuteParallelAsync(); //< ---fire and forget 
        SomeMethod();
        SomeMethod();

        Console.ReadLine();
    }

    private void SomeMethod() => Console.WriteLine($"SomeMethod");

    private async void ExecuteParallelAsync()
    {
        Console.WriteLine($"START");
        await Task.Delay(TimeSpan.FromMilliseconds(100));
        Console.WriteLine($"END");
    }

Вывод

SomeMethod
SomeMethod
START
SomeMethod
SomeMethod
END

Шаг 2, попытка исправить

"О, да, я забыл дождаться своей функции"

await ExecuteParallelAsync(); //Change 1. "I forgot to await"

Выход

Compilation error (line 12, col 3): The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
Compilation error (line 12, col 3): Cannot await 'void'

Шаг 3, исправление

Устранение обеих ошибок из шага 2.

// Change 2
// was:public void Main()  
// which caused: "The 'await' operator can only (...)"
public async Task Main() 
(...)
// Change 3
// was: private async void ExecuteParallelAsync()
// which caused "Compilation error (line 12, col 3): Cannot await 'void'"
private async Task ExecuteParallelAsync()

Выполнение.

Вывод:

SomeMethod
SomeMethod
START
END
SomeMethod
SomeMethod

Для справки, мы могли бы ожидать ExecuteParallelAsync без ключевого слова await, но await - правильный путь во всех сценариях, кроме tiny меньшинство там, где его нет.

...