Qual é a diferença entre Task.Start / Wait e Async / Await?

206

Posso estar faltando alguma coisa, mas qual é a diferença entre fazer:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
fonte

Respostas:

395

Eu posso estar perdendo alguma coisa

Tu es.

qual é a diferença entre doing Task.Waite await task?

Você pede seu almoço no garçom do restaurante. Um momento depois de fazer seu pedido, um amigo entra e senta-se ao seu lado e inicia uma conversa. Agora você tem duas opções. Você pode ignorar seu amigo até que a tarefa seja concluída - você pode esperar até que sua sopa chegue e não fazer mais nada enquanto estiver esperando. Ou você pode responder ao seu amigo e, quando ele parar de falar, o garçom lhe trará sua sopa.

Task.Waitbloqueia até que a tarefa seja concluída - você ignora seu amigo até que a tarefa seja concluída. awaitmantém o processamento de mensagens na fila de mensagens e, quando a tarefa é concluída, enfileira uma mensagem que diz "retome de onde você parou depois que a espera". Você fala com seu amigo e, quando há uma pausa na conversa, a sopa chega.

Eric Lippert
fonte
5
@ronag Não, não é. Como você gostaria que aguardar um Taskque leva 10 ms realmente executasse 10 horas Taskno seu encadeamento, bloqueando você por 10 horas inteiras?
svick
62
@StrugglingCoder: o operador de espera não faz nada, exceto avaliar seu operando e, em seguida, retornar imediatamente uma tarefa ao chamador atual . As pessoas pensam que a assincronia só pode ser obtida com a transferência de trabalho para threads, mas isso é falso. Você pode preparar o café da manhã e ler o jornal enquanto a torrada está na torradeira sem contratar um cozinheiro para assistir à torradeira. As pessoas dizem que, bem, deve haver um fio - um trabalhador - escondido dentro da torradeira, mas garanto que, se você olhar na sua torradeira, não há carinha ali assistindo a torrada.
precisa
11
@StrugglingCoder: Então, quem está fazendo o trabalho que você pergunta? Talvez outro thread esteja fazendo o trabalho, e esse segmento tenha sido atribuído a uma CPU, portanto o trabalho está realmente sendo feito. Talvez o trabalho esteja sendo feito por hardware e não exista nenhum encadeamento. Mas certamente, você diz, deve haver alguma discussão no hardware . Não. O hardware existe abaixo do nível dos threads. Não precisa haver discussão! Você pode se beneficiar da leitura do artigo de Stephen Cleary, Não há discussão.
precisa
6
@StrugglingCoder: Agora, pergunta, suponha que haja trabalho assíncrono sendo feito e que não haja hardware e que não haja outro encadeamento. Como isso é possível? Bem, suponha que o que você esperava enfileire uma série de mensagens do Windows , cada uma das quais funciona um pouco? Agora o que acontece? Você retorna o controle para o loop de mensagens, ele começa a puxar as mensagens para fora da fila, fazendo um pouco de trabalho a cada vez, e o último trabalho realizado é "executar a continuação da tarefa". Nenhuma discussão extra!
22616 Eric
8
@ StrugglingCoder: Agora, pense no que acabei de dizer. Você já sabe que é assim que o Windows funciona . Você executa uma série de movimentos do mouse e cliques de botão e outros enfeites. As mensagens são enfileiradas, processadas por sua vez, cada mensagem faz com que seja executada uma pequena quantidade de trabalho e, quando tudo estiver pronto, o sistema continuará. A assinatura assíncrona em um thread nada mais é do que você já está acostumado: dividir grandes tarefas em pequenos pedaços, enfileirando-as e executando todos os pequenos bits em alguma ordem. Algumas dessas execuções fazem com que outros trabalhos sejam colocados na fila e a vida continua. Uma discussão!
22716 Eric Clippert
121

Para demonstrar a resposta de Eric, aqui está um código:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
fonte
27
+1 para o código (é melhor executar uma vez do que ler centenas de vezes). Mas a frase " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" é enganosa. Ao pressionar o botão com o t.Wait();manipulador de eventos click no botão ButtonClick(), não é possível pressionar nada e ver algo no console e atualização do rótulo "até que esta tarefa seja concluída", pois a GUI está congelada e não responde, ou seja, cliques ou interações com a GUI estão sendo PERDIDO até a conclusão da tarefa de espera
Gennady Vanin Геннадий Ванин
2
Eu acho que Eric assume que você tem um entendimento básico da API da tarefa. Eu olho para esse código e digo para mim mesmo: "Sim, o t.Waitbloco principal será bloqueado até que a tarefa seja concluída".
The Muffin Man
50

Este exemplo demonstra a diferença muito claramente. Com assíncrono / espera, o segmento de chamada não será bloqueado e continuará executando.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

Saída DoAsTask:

[1] Início do programa
[1] 1 - Iniciando
[1] 2 - Tarefa iniciada
[3] A - começou algo
[3] B - completou algo
[1] 3 - Tarefa concluída com resultado: 123
[1] Fim do programa

Saída DoAsAsync:

[1] Início do programa
[1] 1 - Iniciando
[1] 2 - Tarefa iniciada
[3] A - começou algo
[1] Fim do programa
[3] B - completou algo
[3] 3 - Tarefa concluída com resultado: 123

Atualização: Exemplo aprimorado, mostrando o ID do encadeamento na saída.

Mas
fonte
4
Mas se eu fizer: new Task (DoAsTask) .Start (); em vez de DoAsAsync (); i obter o mesmo functionalety, então onde está o benefício de aguardam ..
omriman12
1
Com sua proposta, o resultado da tarefa deve ser avaliado em outro lugar, talvez outro método ou um lambda. A async-waitit facilita o acompanhamento do código assíncrono. É apenas um aprimorador de sintaxe.
Mas
@ Mas eu não entendo por que o Fim do Programa está depois de A - Começou alguma coisa. Do meu entendimento, quando se trata de aguardar o processo de palavras-chave, deve-se imediatamente ao contexto principal e depois voltar.
@JimmyJimm Do meu entendimento, Task.Factory.StartNew irá gerar um novo thread para executar DoSomethingThatTakesTime. Como tal, não há garantia de que o Final do Programa ou Algo Iniciado com A seja executado primeiro.
RiaanDP
@ JimmyJimm: Eu atualizei a amostra para mostrar os IDs de threads. Como você pode ver, "Fim do programa" e "Algo iniciado - A" estão sendo executados em diferentes segmentos. Então, na verdade, a ordem não é determinística.
Mas
10

Wait () fará com que o código potencialmente assíncrono seja executado de maneira sincronizada. aguardar não.

Por exemplo, você tem um aplicativo Web asp.net. O usuário A chama o terminal / getUser / 1. O pool de aplicativos asp.net selecionará um thread do pool de threads (Thread1) e, esse thread fará uma chamada http. Se você esperar (), esse encadeamento será bloqueado até que a chamada http seja resolvida. Enquanto estiver aguardando, se o UsuárioB chamar / getUser / 2, o pool de aplicativos precisará atender outro encadeamento (Thread2) para fazer a chamada http novamente. Você acabou de criar (bem, buscado no pool de aplicativos, na verdade) outro encadeamento sem motivo, porque você não pode usar o Thread1, pois foi bloqueado por Wait ().

Se você usar aguardar no Thread1, o SyncContext gerenciará a sincronização entre o Thread1 e a chamada http. Simplesmente, ele será notificado assim que a chamada http for concluída. Enquanto isso, se o UsuárioB chamar / getUser / 2, você usará o Thread1 novamente para fazer uma chamada http, porque foi liberada uma vez que a espera foi atingida. Em seguida, outro pedido pode usá-lo, ainda mais. Depois que a chamada http é concluída (usuário1 ou usuário2), o Thread1 pode obter o resultado e retornar ao chamador (cliente). O Thread1 foi usado para várias tarefas.

Teoman shipahi
fonte
9

Neste exemplo, não muito, praticamente. Se você estiver aguardando uma tarefa que retorne em um encadeamento diferente (como uma chamada WCF) ou renuncie ao controle do sistema operacional (como IO do arquivo), aguardar utilizará menos recursos do sistema, não bloqueando um encadeamento.

foson
fonte
3

No exemplo acima, você pode usar "TaskCreationOptions.HideScheduler" e modificar bastante o método "DoAsTask". O método em si não é assíncrono, como acontece com "DoAsAsync" porque retorna um valor de "Tarefa" e é marcado como "assíncrono", fazendo várias combinações. É assim que me dá exatamente o mesmo que usar "async / waitit" :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
user8545699
fonte