Pergunta

Eu não vejo a diferença entre o C#'s (e VB) novas assíncrona recursos, e .NET 4.0 Task Parallel Library.Tome-se, por exemplo, Eric Lippert do código de a partir daqui:

async void ArchiveDocuments(List<Url> urls) {
    Task archive = null;
    for(int i = 0; i < urls.Count; ++i) {
        var document = await FetchAsync(urls[i]);
        if (archive != null)
            await archive;
        archive = ArchiveAsync(document);
    }
}

Parece que o await palavra-chave está a servir para duas finalidades diferentes.A primeira ocorrência (FetchAsync) parece significar, "Se esse valor é usado mais tarde, no método e a sua tarefa não é concluída, espere até que ela seja concluída antes de continuar." A segunda instância (archive) parece significar, "Se essa tarefa ainda não está concluída, espere agora até que termine." Se eu estiver errado, por favor me corrija.

Não poderia tão facilmente ser escrito como este?

void ArchiveDocuments(List<Url> urls) {
    for(int i = 0; i < urls.Count; ++i) {
        var document = FetchAsync(urls[i]);       // removed await
        if (archive != null)
            archive.Wait();                       // changed to .Wait()
        archive = ArchiveAsync(document.Result);  // added .Result
    }
}

Eu já substituiu o primeiro await com um Task.Result onde o valor é realmente necessário, e a segunda await com Task.Wait(), onde a espera é realmente ocorrendo.A funcionalidade é (1) já implementadas, e (2) muito mais semanticamente para o que realmente está acontecendo no código.

Eu sei que um async o método é reescrito como uma máquina de estado, semelhante ao iteradores, mas também não vejo quais os benefícios que traz.Qualquer código que requer outro thread para operar (como baixar) ainda necessita de outro thread, e qualquer código que não (tais como a leitura de um arquivo) pode ainda utilizar o TPL para trabalhar com apenas uma única thread.

Eu estou obviamente a falta de algo muito grande aqui;alguém pode me ajudar a entender isso um pouco melhor?

Foi útil?

Solução

Eu acho que o mal -entendido surge aqui:

Parece que a palavra -chave Aguardista está servindo dois propósitos diferentes. A primeira ocorrência (Fetchasync) parece significar: "Se esse valor for usado posteriormente no método e sua tarefa não terminar, aguarde até que ela seja concluída antes de continuar". A segunda instância (arquivo) parece significar: "Se essa tarefa ainda não estiver concluída, espere agora até que ela seja concluída". Se eu estiver errado, por favor me corrija.

Isso é realmente completamente incorreto. Ambos têm o mesmo significado.

No seu primeiro caso:

var document = await FetchAsync(urls[i]);

O que acontece aqui é que o tempo de execução diz "comece a chamar Fetchasync e retorne o ponto de execução atual para o fio que chama esse método". Não há "espera" aqui - em vez disso, a execução retorna ao contexto de sincronização de chamadas e as coisas continuam agitando. Em algum momento do futuro, a tarefa do Fetchasync será concluída e, nesse ponto, esse código será retomado no contexto de sincronização do encadeamento de chamadas, e a próxima instrução (atribuindo a variável do documento) ocorrerá.

A execução continuará até a segunda chamada aguardar - em que momento, a mesma coisa acontecerá - se o Task<T> (Arquivo) Não está completo, a execução será liberada no contexto de chamada - caso contrário, o arquivo será definido.

No segundo caso, as coisas são muito diferentes - aqui, você está bloqueando explicitamente, o que significa que o contexto de sincronização de chamadas nunca terá a chance de executar qualquer código até que todo o seu método seja concluído. É verdade que ainda há assincronia, mas a assincronia está completamente contida nesse bloco de código - nenhum código fora desse código colado acontecerá neste thread até que todo o seu código seja concluído.

Outras dicas

Há uma enorme diferença:

Wait() blocos, await não bloqueia. Se você executar a versão assíncrona de ArchiveDocuments() No seu tópico da GUI, a GUI permanecerá responsiva enquanto as operações de busca e arquivamento estão em execução. Se você usar a versão TPL com Wait(), sua GUI será bloqueada.

Observe que async consegue fazer isso sem introduzir nenhum tópico - no ponto do await, o controle é simplesmente devolvido ao loop da mensagem. Uma vez que a tarefa está sendo esperada, o restante do método (continuação) é inserido no loop da mensagem e o tópico da GUI continuará correndo ArchiveDocuments de onde parou.

Anders se resumia a uma resposta muito sucinta na entrevista ao vivo do Channel 9. Eu recomendo

As novas palavras -chave assíncronas e aguardas permitem que você orquestrar concorrência em seus aplicativos. Na verdade, eles não introduzem nenhuma simultaneidade no seu aplicativo.

TPL e mais especificamente a tarefa é mão única Você pode usar para realmente executar operações simultaneamente. A nova palavra -chave assíncrona e aguardar permitem que você compor Essas operações simultâneas de maneira "síncrona" ou "linear".

Portanto, você ainda pode escrever um fluxo linear de controle em seus programas, enquanto a computação real pode ou não acontecer simultaneamente. Quando a computação acontece simultaneamente, aguarde e assíncico permitem que você compor essas operações.

A capacidade de transformar o fluxo de controle do programa em uma máquina de estado é o que torna essas novas palavras -chave interessantes. Pense nisso como rendimento de controle, em vez de valores.

Verificação de saída Este vídeo do canal 9 de Anders falando sobre o novo recurso.

O problema aqui é que a assinatura de ArchiveDocuments é enganoso. Tem um retorno explícito de void Mas realmente o retorno é Task. Para mim, o vazio implica síncrono, pois não há como "esperar" para terminar. Considere a assinatura alternativa da função.

async Task ArchiveDocuments(List<Url> urls) { 
  ...
}

Para mim, quando está escrito dessa maneira, a diferença é muito mais óbvia. o ArchiveDocuments A função não é aquela que completa síncrona, mas terminará mais tarde.

A chamada para FetchAsync() ainda vai bloquear até conclui-lo (a menos que uma declaração no prazo de chamadas await?) A chave é que o controle é retornado ao chamador (porque o ArchiveDocuments o método em si é declarada como async).Assim, o chamador pode continuar felizes processamento da lógica da INTERFACE do usuário, responder a eventos, etc.

Quando FetchAsync() conclui, ele interrompe o chamador para terminar o ciclo.Bate ArchiveAsync() e blocos, mas ArchiveAsync() provavelmente, apenas cria uma nova tarefa, começa ele, e retorna a tarefa.Isso permite que o segundo ciclo para começar, enquanto a tarefa está a processar.

O segundo ciclo hits FetchAsync() e blocos, retornando o controle para o chamador.Quando FetchAsync() conclui, novamente interrompe o chamador para continuar o processamento.Em seguida, ele atinge await archive, que devolve o controle para o chamador até o Task criado em loop 1 conclui.Uma vez que a tarefa é concluída, o chamador é novamente interrompido, e o segundo loop chama ArchiveAsync(), que recebe uma tarefa iniciada e começa loop 3, repita ad nauseum.

A chave está retornando o controle para o chamador enquanto o grande levantadores estão em execução.

A palavra -chave aguarda não introduz concorrência. É como a palavra -chave de rendimento, diz ao compilador para reestruturar seu código no Lambda controlado por uma máquina de estado.

Para ver como seria o código aguardar sem 'aguardar', veja este excelente link: http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await.aspx

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top