Execute duas tarefas assíncronas em paralelo e colete os resultados no .NET 4.5

116

Estou tentando há um tempo algo que achei que seria simples trabalhar com .NET 4.5

Quero iniciar duas tarefas de longa duração ao mesmo tempo e coletar os
resultados da melhor maneira C # 4.5 (RTM)

O seguinte funciona, mas não gosto porque:

  • Eu quero Sleepser um método assíncrono para que possa awaitoutros métodos
  • Parece desajeitado com Task.Run()
  • Não acho que isso esteja usando novos recursos de linguagem!

Código de trabalho:

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;
}

Código não funcional:

Atualização: Isso realmente funciona e é a maneira correta de fazer isso, o único problema é o Thread.Sleep

Este código não funciona porque a chamada para Sleep(5000)inicia imediatamente a execução da tarefa, portanto, Sleep(1000)não é executada até que seja concluída. Isto é verdade mesmo que Sleepé asynce eu não estou usando awaitou chamar .Resultmuito cedo.

Eu pensei que talvez houvesse uma maneira de obter uma não execução Task<T>chamando um asyncmétodo para que eu pudesse chamar Start()as duas tarefas, mas não consigo descobrir como obter um Task<T>chamando um método assíncrono.

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
fonte
nota: tornar Go um método assíncrono não faz diferença
Simon_Weaver
3
O bloqueio está acontecendo em task1.Resultnão em var task1 = Sleep(5000)porque seu método Sleep sem uma palavra-chave await é síncrono.
Arvis

Respostas:

86

Você deve usar Task.Delay em vez de Sleep para programação assíncrona e, em seguida, usar Task.WhenAll para combinar os resultados da tarefa. As tarefas seriam executadas em paralelo.

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
fonte
11
Esta é uma ótima resposta ... mas eu pensei que essa resposta errada era até executá-la. então eu entendi. Realmente é executado em 5 segundos. O truque é NÃO aguardar as tarefas imediatamente, em vez disso, esperar em Task.WhenAll.
Tim Lovell-Smith,
113
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
}
Bart
fonte
2
Eu +1 porque você declara t1, t2 como Tarefa, que é o caminho certo.
Minime de
12
Acredito que esta solução requer que o método Go também seja assíncrono, o que significa que expõe a capacidade de ser assíncrono. Se você quiser algo mais parecido com o caso de quem pede, em que o Gométodo do chamador é síncrono, mas deseja concluir duas tarefas independentes de forma assíncrona (ou seja, nenhuma precisa ser concluída antes da outra, mas ambas devem ser concluídas antes que a execução continue), então Task.WaitAllseria melhor, e você não não precisa da palavra-chave await, portanto, não é necessário que o Gométodo de chamada seja assíncrono. Nenhuma das abordagens é melhor, é apenas uma questão de qual é o seu objetivo.
AaronLS 01 de
1
async void LongTask1() {...}Método nulo : não tem propriedade Task.Result. Use Task sem T em tal caso: async Task LongTask1().
Arvis
Não obtive os resultados de nenhuma das tarefas. Então mudei para Task<TResult> t1 = LongTask1();e agora entendi t1.Result. <TResult>é o tipo de retorno do seu resultado. Você precisará de um return <TResult>em seu método para que isso funcione.
gilu
1
Pode valer a pena mencionar que se você estiver fazendo coisas realmente simples e não quiser o extra t1e as t2variáveis, você pode usar new Task(...). Por exemplo: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Uma pegadinha dessa abordagem é que o compilador não reconhecerá que a variável foi atribuída e a tratará como não atribuída se você não fornecer um valor inicial.
Robert Dennis
3

Embora seu Sleepmétodo seja assíncrono, Thread.Sleepnão é. A ideia do assíncrono é reutilizar um único thread, não iniciar vários threads. Como você bloqueou usando uma chamada síncrona para Thread.Sleep, não vai funcionar.

Estou assumindo que Thread.Sleepé uma simplificação do que você realmente deseja fazer. Sua implementação real pode ser codificada como métodos assíncronos?

Se você precisar executar várias chamadas de bloqueio síncronas, procure outro lugar, eu acho!

Richard
fonte
obrigado Richard - sim, parece funcionar conforme o esperado quando eu realmente uso minha chamada de serviço
Simon_Weaver
então como executar assíncrono? Eu tenho um aplicativo que faz muita troca de arquivos e espera pelo arquivo, em torno de 5 segundos, e então outro processo, quando eu "quando para todos" ele executa primeiro primeiro, depois, segundo, mesmo que eu tenha dito:, var x = y()e não var x=await y()ou y().wait()ainda espere todo o caminho e se o async não resolver isso sozinho, o que devo fazer? NOTE que y é decorado com assíncrono e espero que faça tudo dentro de quando tudo, não exatamente onde foi atribuído, EDITAR: Eu apenas quando meu parceiro disse, vamos tentar Task.Factory, e ele disse que funcionou quando eu sair lado desta classe
deadManN
2

Para responder a este ponto:

Eu quero que Sleep seja um método assíncrono para que possa aguardar outros métodos

você pode reescrever a Sleepfunção desta forma:

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();
}

executar este código resultará em:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
asidis
fonte
2

É fim de semana agora!

    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
fonte
0

Este artigo ajudou a explicar muitas coisas. É no estilo FAQ.

Async / Await FAQ

Esta parte explica porque Thread.Sleepé executado no mesmo thread original - levando à minha confusão inicial.

A palavra-chave “async” causa a invocação de um método para enfileirar no ThreadPool? Para criar um novo tópico? Para lançar um foguete para Marte?

Não. Não. E não. Veja as questões anteriores. A palavra-chave “async” indica ao compilador que “await” pode ser usado dentro do método, de forma que o método possa ser suspenso em um ponto de await e ter sua execução reiniciada de forma assíncrona quando a instância esperada for concluída. É por isso que o compilador emite um aviso se não houver “espera” dentro de um método marcado como “assíncrono”.

Simon_Weaver
fonte