assíncrono e aguardar - pesquisa de alternativas [fechada]


15

Agora que sabemos o que está reservado para o c # 5, aparentemente ainda há uma abertura para influenciarmos a escolha das duas novas palavras-chave para ' Assíncronia ' que foram anunciadas ontem por Anders Heijsberg no PDC10 .

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

Eric Lippert tem uma explicação sobre a escolha das duas palavras-chave atuais e a maneira como elas foram mal interpretadas nos estudos de usabilidade. Os comentários têm várias outras proposições.

Por favor - uma sugestão por resposta, duplicatas serão bloqueadas.


A propósito, a "programação assíncrona integrada à linguagem" nos dá o LIAP, não sai da língua da mesma maneira que o LINQ;) #
2155 Benjol Benjol

1
A menos que seu pronunciado salto.
Conrad Frix

3
Mas "Tempo de execução assíncrono integrado ao idioma" é muito bom para acrônimos.
glenatron

Isso tem que ser fora de tópico.
DeadMG

Respostas:


6

Dado que não sou claro sobre o significado / necessidade de async, não posso discutir com isso, mas minha melhor sugestão para substituir awaité:

yield while (veja! sem novas palavras-chave)

Note que, pensando um pouco mais sobre isso, pergunto-me se reutilizar whiledessa maneira é uma boa idéia - a tendência natural seria esperar um boleano posteriormente.

(Pensa: encontrar boas palavras-chave é como encontrar bons nomes de domínio :)


+1 e, a propósito, você me derrotou a comentar em seu blog por 7 minutos ...
Nota para si mesmo - pense em um nome

Mas você não está necessariamente produzindo execução se a tarefa já tiver sido concluída. Mas você está sempre aguardando a conclusão da tarefa (embora nunca esteja esperando).
Allon Guralnek

@ Allon Você também não executa necessariamente o corpo do loop while(x) {...}, se xfor falso.
Nota para auto-pensar em um nome

@ Nota: Bem, não há verbo while. Se você adicionar um verbo, como do, então você obtém do {...} while (x), que executa o corpo independentemente de x (pelo menos uma vez). Sua sugestão yield whileparece muito semelhante do while, mas com garantias opostas de execução do verbo, o que pode ser um pouco enganador (mas não tanto assim). O que mais detesto yieldé que implica a implementação de um mecanismo. O ponto principal de async/ awaité que você escreve uma operação assíncrona em um estilo síncrono. yieldquebra esse estilo síncrono.
Allon Guralnek 30/10/10

Uma nova palavra-chave é necessariamente uma coisa ruim? Pelo que entendi, a awaitpalavra - chave seria reconhecida pelo contexto, para que você ainda possa ter um método ou variável chamado "aguardar", se desejar. Até certo ponto, acho que usar uma nova palavra-chave para novas funcionalidades é menos confusa do que reutilizar uma palavra-chave existente para significar mais de uma coisa. (exemplo exagerado: dangermouse.net/esoteric/ook.html )
Tim Goodman

5

Que tal não ter uma palavra-chave?

Gostaria que o compilador percebesse que, na maioria das vezes, quando chamo um método assíncrono, quero o resultado.

Document doc = DownloadDocumentAsync();

É isso aí. A razão pela qual as pessoas estão tendo dificuldade em pensar em uma palavra-chave para isso é porque é como ter uma palavra-chave para "faça o que você faria se as coisas fossem perfeitamente normais". Esse deve ser o padrão, não requer uma palavra-chave.

Atualizar

Originalmente, sugeri que o compilador deveria ficar esperto com a inferência de tipo para descobrir o que fazer. Pensando mais sobre isso, eu manteria a implementação existente no CTP como ela é, mas faria algumas adições triviais, para reduzir os casos em que você precisaria usar a awaitpalavra - chave explicitamente.

Nós inventamos um atributo: [AutoAwait]. Isso só pode ser aplicado a métodos. Uma maneira de aplicar isso ao seu método é marcá-lo async. Mas você também pode fazer isso manualmente, por exemplo:

[AutoAwait]
public Task<Document> DownloadDocumentAsync()

Então, dentro de qualquer asyncmétodo, o compilador assumirá que você deseja aguardar uma chamada DownloadDocumentAsync, para que você não precise especificá-lo. Qualquer chamada para esse método o aguardará automaticamente.

Document doc = DownloadDocumentAsync();

Agora, se você deseja "ficar esperto" e obter o Task<Document>, use um operador start, que só pode aparecer antes de uma chamada de método:

Task<Document> task = start DownloadDocumentAsync();

Legal, eu acho. Agora, uma chamada simples de método significa o que normalmente significa: aguarde a conclusão do método. E startindica algo diferente: não espere.

Para o código que aparece fora de um asyncmétodo, a única maneira de você chamar um [AutoAwait]método é prefixando-o start. Isso obriga a escrever um código com o mesmo significado, independentemente de ele aparecer asyncou não em um método.

Então eu começo a ficar ganancioso! :)

Em primeiro lugar, quero asyncaplicar aos métodos de interface:

interface IThing
{
    async int GetCount();
} 

Basicamente, significa que o método de implementação deve retornar Task<int>ou algo compatível awaite os chamadores do método terão [AutoAwait]comportamento.

Além disso, quando eu implemento o método acima, desejo poder escrever:

async int GetCount()

Portanto, não tenho que mencionar Task<int>como o tipo de retorno.

Além disso, quero asyncaplicar a tipos de delegação (que, afinal, são como interfaces com um método). Então:

public async delegate TResult AsyncFunc<TResult>();

Um asyncdelegado tem - você adivinhou - [AutoAwait]comportamento. A partir de um asyncmétodo, você pode chamá-lo e ele será awaiteditado automaticamente (a menos que você escolha apenas start). E então, se você disser:

AsyncFunc<Document> getDoc = DownloadDocumentAsync;

Isso simplesmente funciona. Não é uma chamada de método. Nenhuma tarefa foi iniciada ainda - uma async delegatenão é uma tarefa. É uma fábrica para fazer tarefas. Você pode dizer:

Document doc = getDoc();

E isso iniciará uma tarefa, esperará que ela termine e dê o resultado. Ou você pode dizer:

Task<Document> t = start getDoc();

Portanto, um lugar onde o "encanamento" vaza é que, se você deseja fazer um delegado para um asyncmétodo, precisa saber usar um async delegatetipo. Então, em vez de Funcvocê deve dizer AsyncFunc, e assim por diante. Embora um dia esse tipo de coisa possa ser corrigido por inferência de tipo aprimorada.

Outra pergunta é o que deveria acontecer se você disser iniciar em um método comum (não assíncrono). Obviamente, um erro de compilação seria a opção segura. Mas existem outras possibilidades.


Isso pode ser possível com uma conversão implícita, mas exigiria uma avaliação da esquerda para a direita (que é exatamente o oposto de como o compilador normalmente funciona, exceto lambdas). Acho que ainda sou contra isso, porque evita o uso de var, potencialmente, a necessidade de substituir algum nome de tipo explícito longo e também é ambíguo entre o awaitcaso e o caso em que alguém acidentalmente chamou o método assíncrono em vez do método síncrono normal. Parece intuitivo a princípio, mas na verdade viola o princípio da menor surpresa.
Aaronaught 1/11/10

@Aaronaught - Por que impede o uso de var? Gostaria de saber se você está respondendo à revisão anterior da minha resposta ... Eu a reescrevi completamente. Agora você pode pensar nessa sugestão da seguinte maneira: se o método estiver marcado com um atributo especial, é como se a awaitpalavra-chave fosse inserida automaticamente na frente das chamadas para esse método (a menos que você a suprima com o startprefixo). Tudo permanece exatamente como no CTP e, portanto, varfunciona bem.
Daniel Earwicker

Na verdade, eu era ... estranho que decidi revisitar esse tópico e responder à sua resposta quase exatamente ao mesmo tempo em que você decidiu editá-lo. Vou ter que relê-lo agora ...
Aaronaught

1
Gosto da sua inversão da palavra-chave wait. E também não gosto da redundância tripla de public async Task<int> FooAsync().
Allon Guralnek 1/11

1
Sim, vejo a convenção de nomenclatura Async-postfix como um sinal de que algo pode ser capturado mais formalmente. Basicamente, se houver uma regra dizendo "métodos como esse devem ser nomeados de uma certa maneira, para que as pessoas saibam como chamá-los adequadamente", segue-se que a mesma regra pode ser usada para atribuir esses métodos de uma certa maneira e, em seguida, o compilador para ajudá-lo a chamá-los corretamente.
Daniel10


4

Eu acho que asyncestá bem, mas talvez seja porque eu o associo a páginas assíncronas do ASP.NET - a mesma idéia.

Para a awaitpalavra - chave eu prefiro continue afterou resume after.

Eu não como yieldou qualquer de suas variantes, porque a semântica são tais que o método pode nunca realmente deu execução; isso depende do estado da tarefa.


Eu gosto resume afterde await. Talvez asyncpossa ser chamado resumable.
Tim Goodman

Embora eu prefira apenas after, a continue afterabordagem tem uma forte vantagem de implementação: inclui uma palavra-chave contextual existente no momento, mas com uma sintaxe incompatível com o uso atual. Isso garante que a adição nunca irá quebrar o código existente. Ao usar uma palavra-chave totalmente nova, a implementação precisa lidar com os possíveis usos da palavra como um identificador no código mais antigo, o que pode ser bastante complicado.
Edurne Pascual 31/03

3

Também adicionei comentários ao blog de Eric, não vejo problemas ao usar a mesma palavra-chave async

var data = async DownloadFileAsync(url);

Estou apenas expressando que quero baixar o arquivo de forma assíncrona. Há um pouco de redundância aqui, "async" aparece duas vezes, porque também está no nome do método. O compilador pode ser mais inteligente e detectar a convenção de que os métodos que terminam em "Async" são métodos assíncronos de fato e acrescentam isso para nós no código compilado. Então, em vez disso, você pode querer ligar

var data = async DownloadFile(url);

em vez de chamar o síncrono

var data = DownloadFile(url);

Caramba, também devemos poder defini-los da mesma maneira, já que a palavra-chave assíncrona está presente em nossa declaração, por que devemos adicionar manualmente "Async" a cada nome de método - o compilador pode fazer isso por nós.


Gosto do açúcar que você adicionou, embora os caras do C # provavelmente não o façam. (FWIW, eles já fazem algo semelhante ao procurar nomes de atributos)
Observação: pense em um nome em

2
E, como a asyncpalavra-chave no método é apenas uma questão de segurança (se eu entendi corretamente), pergunto-me se a melhor coisa a fazer não seria fazer o oposto do que você sugere: abandone asynco método e apenas o use. onde eles estão atualmente await.
Benjol 30/10/10

Isso é uma possibilidade. A palavra-chave assíncrona no processo de remoção do método existe apenas para limpeza. Eu preferiria que fosse mantido lá, mas sem a necessidade de adicionar "Async" aos nomes dos métodos. Por exemplo, em async Task<Byte[]> DownloadFile(...)vez de Task<Byte[]> DownloadFileAsync(...)(a última será a assinatura compilada de qualquer maneira). De qualquer maneira funciona.
Mark H

Sinceramente, também não sou fã disso. Como em um comentário anterior, devo salientar que sua versão final viola o princípio da menor surpresa, pois na verdade está invocando um método completamente diferente do escrito, e a implementação e assinatura desse método dependem inteiramente da classe de implementação . Até a primeira versão é problemática porque não está realmente dizendo nada (executa de forma assíncrona esse método assíncrono?). O pensamento que estamos tentando expressar é uma continuação ou execução adiada e isso não expressa nada disso.
Aaronaught 1/11/10

3

async = task - Está modificando uma função para retornar uma tarefa. Por que não usar a palavra-chave "task"?

waitit = finish - Não precisamos necessariamente esperar, mas a tarefa precisa "terminar" antes de usar o resultado.


É realmente difícil argumentar com a simplicidade aqui.
sblom

2

Eu gosto yield until. yield while, já sugerido, é ótimo e não apresenta novas palavras-chave, mas acho que "até" captura o comportamento um pouco melhor.

Eu acho que yield <something>é uma ótima idéia, porque o rendimento já captura a idéia de tornar o restante do método uma continuação tão bem. Talvez alguém possa pensar em uma palavra melhor do que "até".


2

Eu só quero registrar meu voto na sugestão de Aaron G comefrom- o primeiro uso apropriado que eu vi da declaração COMEFROM da INTERCAL . A idéia é que é o oposto do GOTO (saltando para longe da instrução GOTO), pois faz com que algum lugar no seu código vá para a instrução COMEFROM.


2

Como estamos lidando com Task<T>s, que tal usar startcomo palavra-chave que precede a declaração, como em:

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


Hmmm, talvez finishseria ainda melhor do que start?
Protagonista

1

Vale ressaltar que o F # também usa a asyncpalavra - chave em seus fluxos de trabalho assíncronos, o que é praticamente a mesma coisa que a nova funcionalidade assíncrona no C # 5. Portanto, eu manteria o mesmo

Para a awaitpalavra - chave em F #, eles apenas usam em let!vez de let. O C # não tem a mesma sintaxe de atribuição, portanto, eles precisam de algo no lado direito do =sinal. Como Benjol disse, funciona da mesma yieldmaneira que deveria ser quase uma variante disso.


1
Um "aguardar" não precisa estar em nenhuma tarefa (embora é claro que normalmente é). É legal como operador em praticamente qualquer expressão que tenha um tipo em que possamos encontrar um GetAwaiter. (As regras exatas estão ainda a ser trabalhado em uma forma publicável.)
Eric Lippert

1
@Eric, em F #, que seria do!, mas você sabia que ...
Benjol

1

yield async FetchAsync(..)


Isso vai perfeitamente com o async modificador que você precisa aplicar no método que está chamando. E também a semântica da corrente, yield returnou seja, você retorna e produz execução para o código enumerador enquanto, nesse caso, está produzindo sua execução para o método assíncrono.

Imagine se, no futuro, houver outros usos para yield , poderíamos adicionar um em yield xque x é o novo recurso brilhante, em vez de termos todas essas palavras-chave diferentes para fazer basicamente a mesma coisa, render execução.

Sinceramente, não entendo bem o argumento 'não rendendo execução'. Afinal, o objetivo de chamar outro método já não é 'render execução' para esse método? Independentemente de ser assíncrono ou não? Estou faltando alguma coisa aqui?

E bom para você se o async retorno de forma síncrona, mas com a palavra-chave lá, significar que há uma chance provável de que o método seja executado de forma assíncrona e que você esteja produzindo execução para outro método. Seu método deve levar isso em consideração, independentemente de o método realmente fazer chamadas assíncronas ou não.

Na IMO, acho que os vários casos "não ceder" são um detalhe de implementação. Eu prefiro garantir a consistência no idioma (ou seja, reutilizar yield).


0

Que tal complete, como em "Quero que a tarefa seja concluída"?

Task<byte[]> downloadTask = DownloadFileAsync(url);
byte[] data = complete downloadTask;

1
Por que o voto negativo? É uma cortesia, pelo menos, explicar-se depois da votação.
Allon Guralnek

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.