Pergunta

Eu tenho acompanhado o novo anúncio sobre o novo async recurso que estará no C# 5.0. Eu tenho um entendimento básico do estilo de passagem de continuação e da transformação que o novo compilador C# faz para codificar como este snippet Postagem de Eric Lippert:

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

Eu sei que alguns idiomas implementam continuações nativamente, por meio de contínua de chamada com corrente (callcc), mas eu realmente não entendo como isso funciona ou o que faz exatamente.

Então, aqui está a pergunta: se Anders et al. decidiu morder a bala e apenas implementar callcc em C# 5.0 em vez do async/await Caso especial, como seria o snippet acima?

Foi útil?

Solução

Resposta original:

Sua pergunta, pelo que entendi, é "e se, em vez de implementar" aguardar "especificamente para a assincronia baseada em tarefas, mas a operação de fluxo de controle mais geral da ligação de chamada com corrente foi implementada?"

Bem, antes de tudo, vamos pensar sobre o que "aguarda". "Wait" toma uma expressão do tipo Task<T>, obtém um aguardador e chama o aguardador com a continuação atual:

await FooAsync()

torna -se efetivamente

var task = FooAsync();
var awaiter = task.GetAwaiter();
awaiter.BeginAwait(somehow get the current continuation);

Agora suponha que tivemos um operador callcc o que toma como argumento é um método e chama o método com a continuação atual. Isso seria assim:

var task = FooAsync();
var awaiter = task.GetAwaiter();
callcc awaiter.BeginAwait;

Em outras palavras:

await FooAsync()

nada mais é do que

callcc FooAsync().GetAwaiter().BeginAwait;

Isso responde à sua pergunta?


Atualização #1:

Como aponta um comentarista, a resposta abaixo pressupõe o padrão de geração de código da versão "Technology Visuew" do recurso Async/Await. Na verdade, geramos código ligeiramente diferente na versão beta do recurso, embora logicamente é o mesmo. O CodeGen atual é algo como:

var task = FooAsync();
var awaiter = task.GetAwaiter();
if (!awaiter.IsCompleted)
{
    awaiter.OnCompleted(somehow get the current continuation);
    // control now returns to the caller; when the task is complete control resumes...
}
// ... here:
result = awaiter.GetResult();
// And now the task builder for the current method is updated with the result.

Observe que isso é um pouco mais complicado e lida com o caso em que você está "aguardando" um resultado que já foi calculado. Não há necessidade de passar por toda a rigamarol de produzir controle para o chamador e pegar novamente de onde você parou, se o resultado que você está esperando já estiver em cache na memória para você ali mesmo.

Assim, a conexão entre "aguardar" e "calcc" não é tão direta quanto na versão de pré -visualização, mas ainda está claro que estamos fazendo um calcc no método "onCompleted" do aguardador. Nós simplesmente não fazemos o calcc se não precisarmos.


Atualização #2:

Como esta resposta

https://stackoverflow.com/a/9826822/88656

De Timwi aponta, a semântica de Call/CC e aguarda não é exatamente a mesma; Uma chamada "verdadeira"/CC exige que "capturemos" o inteira continuação de um método incluindo toda a pilha de chamadas, ou equivalentemente que todo o programa seja reescrito no estilo de passagem de continuação.

O recurso "Aguardar" é mais como uma "chamada cooperativa/CC"; A continuação captura apenas "qual é a corrente Método de retorno de tarefas prestes a fazer a seguir no ponto do aguardar? "Se o chamador do método de retorno de tarefas fará algo interessante depois que a tarefa estiver concluída, então é livre para se inscrever sua continuação como a continuação da tarefa.

Outras dicas

Não sou especialista em continuações, mas vou fazer uma facada para explicar a diferença entre assíncrono/aguardar e ligar/cc. É claro que essa explicação supõe que eu entendo a chamada/cc e assync/aguarda, o que não tenho certeza se. No entanto, aqui vai ...

Com C# 'Async', você está dizendo ao compilador para gerar uma versão especial desse método específico que entende como engarrafar seu estado em uma estrutura de dados de heap-data, para que possa ser "removido da pilha real" e retomada posteriormente. Dentro de um contexto assíncrono, "Wait" é como "CHAMADA/CC", pois usa os objetos gerados pelo compilador para engarrafar o estado e sair da "pilha real" até que a tarefa seja concluída. No entanto, como é a reescrita do compilador de um método assíncrono que permite que o estado seja engarrafado, aguarda só pode ser usado em um contexto assíncrono.

Na chamada/CC de primeira classe, o tempo de execução do idioma gera todo o código de modo que a continuação atual possa ser engarrafada em uma função de continuação de chamada (tornando a palavra-chave assíncrona desnecessária). Chamada/CC ainda age como aguarda, fazendo com que o estado de continuação de corrente (pense no estado da pilha) seja botado e repassado como uma função da função chamada. Uma maneira de fazer isso é usar os quadros de heap para tudo Invocação da função, em vez de quadros de 'pilha'. (às vezes chamado de 'pilha', como em 'python sem pilha' ou em muitas implementações de esquema) de outra maneira é remover todos os dados da "pilha real" e enchê-lo em uma estrutura de dados de heap antes de ligar para o alvo de chamada/CC .

Alguns problemas complicados podem surgir se houver chamadas para funções externas (pense em dllimport) misturadas na pilha. Eu suspeito que essa é a razão pela qual eles foram com a implementação assíncrona/aguardando.

http://www.madore.org/~david/computers/callcc.html


Como em C#, uma função deve ser marcada como "assíncrona" para usar essas mecânicas, eu me pergunto se essa palavra -chave assíncrona se tornará um vírus que se espalha para muitas funções em muitas bibliotecas. Se isso acontecer. Eles podem eventualmente perceber que devem implementar chamadas/CC de primeira classe no nível da VM, em vez deste modelo assíncrono baseado em compilador-rei. Só o tempo irá dizer. No entanto, é certamente uma ferramenta útil no contexto do ambiente C# atual.

Meio inútil, eu diria. Os intérpretes do esquema geralmente implementam a chamada/CC com uma máquina de estado que captura o estado local na pilha. Qual é exatamente O que C# 5.0 (ou mais corretamente C# 2.0 do iterador) também. Elas fez Implementar chamada/CC, a abstração que eles criaram é bastante elegante.

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