PrevTask.Wait () é recomendado para ser usado com ContinueWith (da biblioteca de tarefas)?

88

Disseram-me recentemente que a forma como eu estava usando meu .ContinueWith for Tasks não era a maneira correta de usá-los. Ainda não encontrei evidências disso na internet, então vou perguntar a vocês e ver qual é a resposta. Aqui está um exemplo de como eu uso .ContinueWith:

public Task DoSomething()
{
    return Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Step 1");
    })
    .ContinueWith((prevTask) =>
    {
        Console.WriteLine("Step 2");
    })
    .ContinueWith((prevTask) =>
    {
        Console.WriteLine("Step 3");
    });
}

Agora eu sei que este é um exemplo simples e será executado muito rápido, mas apenas suponha que cada tarefa execute uma operação mais longa. Então, o que me disseram é que no .ContinueWith, você precisa dizer prevTask.Wait (); caso contrário, você pode trabalhar antes que a tarefa anterior termine. É mesmo possível? Presumi que minha segunda e terceira tarefas só seriam executadas quando a tarefa anterior terminasse.

O que me disseram como escrever o código:

public Task DoSomething()
{
    return Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Step 1");
    })
    .ContinueWith((prevTask) =>
    {
        prevTask.Wait();
        Console.WriteLine("Step 2");
    })
    .ContinueWith((prevTask) =>
    {
        prevTask.Wait();
        Console.WriteLine("Step 3");
    });
}
Travyguy9
fonte

Respostas:

115

Ehhh .... Acho que algumas das respostas atuais estão faltando alguma coisa: o que acontece com as exceções?

A única razão pela qual você chamaria Waituma continuação seria observar uma exceção potencial do antecedente na própria continuação. A mesma observação aconteceria se você acessasse Resultno caso de a Task<T>e também se acessasse manualmente a Exceptionpropriedade. Francamente, eu não chamaria Waitou acessaria Resultporque, se houver uma exceção, você pagará o preço de aumentá-la novamente, o que é um overhead desnecessário. Em vez disso, você pode apenas verificar a IsFaultedpropriedade do antecedente Task. Alternativamente, você pode criar fluxos de trabalho bifurcados encadeando em várias continuações de irmãos que só disparam com base no sucesso ou falha com TaskContinuationOptions.OnlyOnRanToCompletione TaskContinuationOptions.OnlyOnFaulted.

Agora, não é necessário observar a exceção do antecedente na continuação, mas você pode não querer que seu fluxo de trabalho avance se, digamos, "Etapa 1" falhou. Nesse caso: a especificação TaskContinuationOptions.NotOnFaultedde suas ContinueWithchamadas evitaria que a lógica de continuação até mesmo fosse acionada.

Lembre-se de que, se suas próprias continuações não observarem a exceção, a pessoa que está aguardando a conclusão desse fluxo de trabalho geral será aquela que observará. Ou eles estão Waitsubindo o Taskrio ou aderiram à sua própria continuação para saber quando ela está completa. Se for o último, sua continuação precisaria usar a lógica de observação mencionada.

Drew Marsh
fonte
2
Por fim, alguém deu uma resposta correta. @ Travyguy9 Por favor, leia esta resposta @DrewMarsh e leia mais sobreTaskContinuationOptions
Jasper
2
Ótima resposta, eu estava procurando por "Lembre-se de que, se suas próprias continuações não observarem a exceção, a pessoa que está aguardando a conclusão desse fluxo de trabalho geral será a única a observá-la." Uma pergunta, porém, quando sua tarefa não é esperada, quem é o garçom padrão? (Não foi possível encontrar a resposta para isso)
Thibault D.
20

Você está usando corretamente.

Cria uma continuação que é executada de forma assíncrona quando a Tarefa de destino é concluída.

Fonte: método Task.ContinueWith (ação como MSDN)

Ter que chamar prevTask.Wait()todas as Task.ContinueWithinvocações parece uma maneira estranha de repetir a lógica desnecessária - isto é, fazer algo para ter "certeza absoluta" porque você na verdade não entende o que um determinado código faz. Como verificar se há um nulo apenas para lançar um ArgumentNullExceptiononde teria sido lançado de qualquer maneira.

Então, não, quem te disse isso está errado e provavelmente não entende porque Task.ContinueWithexiste.

Anders Arpi
fonte
16

Quem te disse isso?

Citando MSDN :

Cria uma continuação que é executada de forma assíncrona quando a Tarefa de destino é concluída.

Além disso, qual seria o propósito de Continue With se não estivesse esperando a conclusão da tarefa anterior?

Você pode até mesmo testá-lo:

Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Step 1");
        Thread.Sleep(2000);
    })
    .ContinueWith((prevTask) =>
    {
        Console.WriteLine("I waited step 1 to be completed!");
    })
    .ContinueWith((prevTask) =>
    {
        Console.WriteLine("Step 3");
    });
ken2k
fonte
5

Do MSDN em dianteTask.Continuewith

A tarefa retornada não será agendada para execução até que a tarefa atual seja concluída. Se os critérios especificados por meio do parâmetro continuationOptions não forem atendidos, a tarefa de continuação será cancelada em vez de agendada.

Eu acho que a maneira que você espera que funcione no primeiro exemplo é a maneira correta.

mclark1129
fonte
2

Você também pode querer considerar o uso de Task.Run em vez de Task.Factory.StartNew.

A postagem no blog de Stephen Cleary e a postagem de Stephen Toub às quais ele faz referência explicam as diferenças. Há também uma discussão nesta resposta .

Bizcad
fonte
4
Não votado porque não aborda a questão real. Acrescenta algum valor, mas deve ser um comentário.
Sinestésico 02 de
0

Ao acessar, Task.Resultvocê está fazendo uma lógica semelhante atask.wait

Sameh
fonte
Sim. Podemos evitar o método Wait (). Mas funciona apenas com tarefas de resultado, por exemplo, Tarefa <bool>
Alexander Ulmaskulov
Não votado porque não aborda a questão real. Acrescenta algum valor, mas deve ser um comentário.
Sinestésico 02 de