Параллельное выполнение двух асинхронных задач и сбор результатов в .NET 4.5

116

Некоторое время я пытался получить то, что, как мне казалось, было бы просто работать с .NET 4.5.

Я хочу запустить две длительные задачи одновременно и собрать
результаты лучшим способом C # 4.5 (RTM)

Следующее работает, но мне это не нравится, потому что:

  • Я хочу Sleepбыть асинхронным методом, чтобы awaitдругие методы
  • Это выглядит неуклюже с Task.Run()
  • Я не думаю, что здесь вообще используются какие-либо новые языковые функции!

Рабочий код:

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Task.Run(() => Sleep(5000));    
    var task2 = Task.Run(() => Sleep(3000));

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}

private static int Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    Console.WriteLine("Sleeping for " + ms + " FINISHED");
    return ms;
}

Нерабочий код:

Обновление: это действительно работает, и это правильный способ сделать это, единственная проблема - это Thread.Sleep

Этот код не работает, потому что вызов Sleep(5000)немедленно запускает задачу, поэтому Sleep(1000)не запускается, пока она не будет завершена. Это верно , даже если Sleepэто asyncи я не использую awaitили позвонив по телефону .Resultслишком рано.

Я подумал, может быть, есть способ отключить запуск Task<T>, вызвав asyncметод, чтобы я мог затем вызватьStart() две задачи, но я не могу понять, как получить Task<T>от вызова асинхронного метода.

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Sleep(5000);    // blocks
    var task2 = Sleep(1000);

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
}

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    return ms;
}
Simon_Weaver
источник
примечание: создание асинхронного метода Go не имеет значения
Simon_Weaver
3
Блокировка происходит в task1.Resultnot at, var task1 = Sleep(5000)потому что ваш метод Sleep без ключевого слова await является синхронным.
Arvis

Ответы:

86

Для асинхронного программирования следует использовать Task.Delay вместо Sleep, а затем использовать Task.WhenAll для объединения результатов задачи. Задачи будут выполняться параллельно.

public class Program
    {
        static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            GoAsync();
            Console.ReadLine();
        }
        public static async void GoAsync()
        {

            Console.WriteLine("Starting");

            var task1 = Sleep(5000);
            var task2 = Sleep(3000);

            int[] result = await Task.WhenAll(task1, task2);

            Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

        }

        private async static Task<int> Sleep(int ms)
        {
            Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
            await Task.Delay(ms);
            Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
            return ms;
        }
    }
softveda
источник
11
Это отличный ответ ... но я думал, что это неправильный ответ, пока не запустил его. тогда я понял. Это действительно выполняется за 5 секунд. Хитрость заключается в том, чтобы НЕ ожидать задачи немедленно, вместо этого ожидайте на Task.WhenAll.
Тим Ловелл-Смит
114
async Task<int> LongTask1() { 
  ...
  return 0; 
}

async Task<int> LongTask2() { 
  ...
  return 1; 
}

...
{
   Task<int> t1 = LongTask1();
   Task<int> t2 = LongTask2();
   await Task.WhenAll(t1,t2);
   //now we have t1.Result and t2.Result
}
Барт
источник
2
Я +1, потому что вы объявляете t1, t2 как Task, и это правильный путь.
Minime
12
Я считаю, что это решение требует, чтобы метод Go также был асинхронным, что означает, что он предоставляет возможность быть асинхронным. Если вам нужно что-то более похожее на случай спрашивающего, где метод вызывающего Goявляется синхронным, но хочет выполнить две независимые задачи асинхронно (т.е. ни одна из них не должна завершаться раньше другой, но обе должны завершиться до продолжения выполнения), тогда Task.WaitAllбыло бы лучше, и вы не должны Ключевое слово await не требуется, поэтому не нужно, чтобы вызывающий Goметод был сам по себе асинхронным. Ни один из подходов не лучше, это просто вопрос вашей цели.
AaronLS 01
1
async void LongTask1() {...}Метод Void: не имеет свойства Task.Result. Используйте задачу без Т в таком случае: async Task LongTask1().
Arvis
Я не получил результатов ни по одной из задач. Я поменял его на Task<TResult> t1 = LongTask1();и теперь получаю t1.Result. <TResult>- это возвращаемый тип вашего результата. return <TResult>Чтобы это работало, вам понадобится метод.
gilu
1
Возможно, стоит упомянуть, что если вы делаете действительно простые вещи и не хотите лишних t1и t2переменных, вы можете использовать new Task(...). Например: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Уловка этого подхода заключается в том, что компилятор не распознает, что переменная была назначена, и будет рассматривать ее как неназначенную, если вы не укажете ей начальное значение.
Роберт Деннис
3

Пока ваш Sleepметод асинхронный, Thread.Sleepэто не так. Вся идея async заключается в повторном использовании одного потока, а не в запуске нескольких потоков. Поскольку вы заблокировали использование синхронного вызова Thread.Sleep, это не сработает.

Я предполагаю, что Thread.Sleepэто упрощение того, что вы действительно хотите делать. Можно ли закодировать вашу фактическую реализацию как асинхронные методы?

Если вам действительно нужно запустить несколько синхронных вызовов блокировки, поищите в другом месте, я думаю!

Ричард
источник
спасибо, Ричард - да, похоже, он работает, как и ожидалось, когда я действительно использую свой сервисный звонок
Simon_Weaver
тогда как запустить async? У меня есть приложение, которое выполняет много переключений файлов и ожидает файла, около 5 секунд, а затем другой процесс, когда я "когда для всех" он сначала запускается сначала, затем второй, хотя я сказал:, var x = y()а не var x=await y()или y().wait()еще подождите, и если async не справится с этим сам по себе, что мне делать? ОБРАТИТЕ ВНИМАНИЕ, что y украшен async, и я ожидаю, что он будет делать все в пределах, когда все, а не прямо там, где он назначен, РЕДАКТИРОВАТЬ: я просто когда моему партнеру сказал, давай попробуем Task.Factory, и он сказал, что это сработало, когда я выйду сторона этого класса
deadManN
2

Чтобы ответить на этот вопрос:

Я хочу, чтобы Sleep был асинхронным методом, чтобы он мог ожидать другие методы

вы можете переписать Sleepфункцию следующим образом:

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    var task = Task.Run(() => Thread.Sleep(ms));
    await task;
    Console.WriteLine("Sleeping for " + ms + "END");
    return ms;
}

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    var task1 = Sleep(2000);
    var task2 = Sleep(1000);

    int totalSlept = task1.Result +task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
    Console.ReadKey();
}

запуск этого кода выведет:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
asidis
источник
2

Сейчас выходные !

    public async void Go()
    {
        Console.WriteLine("Start fosterage...");

        var t1 = Sleep(5000, "Kevin");
        var t2 = Sleep(3000, "Jerry");
        var result = await Task.WhenAll(t1, t2);

        Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
        Console.WriteLine("Press any key and take same beer...");
        Console.ReadKey();
    }

    private static async Task<int> Sleep(int ms, string name)
    {
            Console.WriteLine($"{name} going to sleep for {ms}ms :)");
            await Task.Delay(ms);
            Console.WriteLine("${name} waked up after {ms}ms :(";
            return ms;
    }
Arvis
источник
0

Эта статья помогла многое объяснить. Это в стиле FAQ.

Async / Await FAQ

Эта часть объясняет, почему Thread.Sleepработает в одном и том же исходном потоке, что привело к моей первоначальной путанице.

Вызывает ли ключевое слово async вызов метода в очередь к ThreadPool? Чтобы создать новую ветку? Чтобы запустить ракетный корабль к Марсу?

Нет. Нет. И нет. См. Предыдущие вопросы. Ключевое слово «async» указывает компилятору, что «ожидание» может использоваться внутри метода, так что метод может приостанавливаться в точке ожидания и возобновлять его выполнение асинхронно, когда ожидаемый экземпляр завершается. Вот почему компилятор выдает предупреждение, если внутри метода, помеченного как «async», нет «ожиданий».

Simon_Weaver
источник