async & await - encuesta de alternativas [cerrado]

15

Ahora que sabemos lo que está reservado para c # 5, aparentemente todavía hay una oportunidad para que podamos influir en la elección de las dos nuevas palabras clave para ' Asincronía ' que Anders Heijsberg anunció ayer en 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 tiene una explicación de la elección de las dos palabras clave actuales, y la forma en que han sido mal entendidas en los estudios de usabilidad. Los comentarios tienen varias otras proposiciones.

Por favor, una sugerencia por respuesta, se duplicarán los duplicados.

Benjol
fuente
Por cierto, la "programación asincrónica integrada en el lenguaje" nos da LIAP, no se sale de la lengua de la misma manera que LINQ;)
Benjol
1
A menos que sea un salto pronunciado.
Conrad Frix
3
Pero "Tiempo de ejecución asincrónico integrado en el lenguaje" se adapta muy bien.
glenatron
Esto tiene que estar fuera de tema.
DeadMG

Respuestas:

6

Dado que no tengo claro el significado / necesidad de async, realmente no puedo discutirlo, pero mi mejor sugerencia para reemplazar awaites:

yield while (mira! no hay nuevas palabras clave)

Tenga en cuenta que después de haber pensado en esto un poco más, me pregunto si reutilizar de whileesta manera es una buena idea; la tendencia natural sería esperar un booleano después.

(Piensa: encontrar buenas palabras clave es como encontrar buenos nombres de dominio :)

Benjol
fuente
1 y por cierto, me vencieron a comentar su artículo del blog de 7 minutos ...
Nota a la libre - pensar en un nombre
Pero no necesariamente está ejecutando la ejecución si la tarea ya se ha completado. Pero siempre estás esperando la finalización de la tarea (aunque nunca esperando).
Allon Guralnek
@Allon No necesariamente ejecuta el cuerpo del bucle de while(x) {...}ninguno, si xes falso.
Nota para uno mismo - piense en un nombre
@Nota: Bueno, no hay verbo en while. Si agrega un verbo, like do, entonces obtiene do {...} while (x), que ejecuta el cuerpo independientemente de x (al menos una vez). Su sugerencia de yield whileparece muy similar a do while, pero con garantías opuestas de realizar el verbo, lo que puede ser un poco engañoso (pero no es un gran problema). Lo que más me disgusta yieldes que implica la implementación de un mecanismo. El punto de async/ awaites que escribes una operación asincrónica en un estilo sincrónico. yieldrompe ese estilo sincrónico.
Allon Guralnek
¿Una nueva palabra clave es necesariamente algo malo? Según tengo entendido, la awaitpalabra clave sería reconocida por el contexto, por lo que aún podría tener un método o variable llamada "esperar" si lo desea. Hasta cierto punto, creo que usar una nueva palabra clave para una nueva funcionalidad es menos confuso que reutilizar una palabra clave existente para significar más de una cosa. (ejemplo exagerado: dangermouse.net/esoteric/ook.html )
Tim Goodman el
5

¿Qué tal no tener una palabra clave?

Me gustaría que el compilador se dé cuenta de que, la mayoría de las veces, cuando llamo a un método asincrónico, quiero el resultado.

Document doc = DownloadDocumentAsync();

Eso es. La razón por la que las personas están teniendo dificultades para pensar una palabra clave para esto es porque es como tener una palabra clave para "hacer lo que haría si las cosas fueran perfectamente normales". Esa debería ser la predeterminada, no requiere una palabra clave.

Actualizar

Originalmente sugerí que el compilador debería ser inteligente con la inferencia de tipos para descubrir qué hacer. Pensando más en esto, mantendría la implementación existente en el CTP tal como es, pero haré un par de adiciones triviales, para reducir los casos en los que necesitaría usar la awaitpalabra clave explícitamente.

Nos inventamos un atributo: [AutoAwait]. Esto solo se puede aplicar a los métodos. Una forma de aplicar esto a su método es marcarlo async. Pero también puede hacerlo a mano, por ejemplo:

[AutoAwait]
public Task<Document> DownloadDocumentAsync()

Luego, dentro de cualquier asyncmétodo, el compilador asumirá que desea esperar una llamada DownloadDocumentAsync, por lo que no necesita especificarlo. Cualquier llamada a ese método lo esperará automáticamente.

Document doc = DownloadDocumentAsync();

Ahora, si desea "ser inteligente" y obtener el Task<Document>, utilice un operador start, que solo puede aparecer antes de una llamada al método:

Task<Document> task = start DownloadDocumentAsync();

Aseado, creo. Ahora, una llamada de método simple significa lo que generalmente significa: esperar a que se complete el método. E startindica algo diferente: no esperes.

Para el código que aparece fuera de un asyncmétodo, la única forma en que puede llamar a un [AutoAwait]método es mediante el prefijo start. Esto lo obliga a escribir código que tenga el mismo significado independientemente de si aparece en un asyncmétodo o no.

¡Entonces empiezo a ponerme codicioso! :)

En primer lugar, quiero asyncaplicar a los métodos de interfaz:

interface IThing
{
    async int GetCount();
} 

Básicamente significa que el método de implementación debe regresar Task<int>o algo compatible await, y las personas que llaman al método obtendrán un [AutoAwait]comportamiento.

Además, cuando implemente el método anterior, quiero poder escribir:

async int GetCount()

Así que no tengo que mencionar Task<int>como tipo de retorno.

Además, quiero asyncaplicar a los tipos de delegados (que, después de todo, son como interfaces con un método). Entonces:

public async delegate TResult AsyncFunc<TResult>();

Un asyncdelegado tiene, lo has adivinado, [AutoAwait]comportamiento. Desde un asyncmétodo puede llamarlo y se editará automáticamente await(a menos que elija simplemente start). Y entonces si dices:

AsyncFunc<Document> getDoc = DownloadDocumentAsync;

Eso solo funciona. No es una llamada al método. Aún no se ha iniciado ninguna tarea, y async delegateno es una tarea. Es una fábrica para hacer tareas. Puedes decir:

Document doc = getDoc();

Y eso comenzará una tarea y esperará a que termine y le dará el resultado. O puedes decir:

Task<Document> t = start getDoc();

Entonces, un lugar en este lugar donde se filtra la "plomería" es que si desea hacer un delegado a un asyncmétodo, debe saber usar un async delegatetipo. Entonces, en lugar de Funcusted debe decir AsyncFunc, y así sucesivamente. Aunque algún día ese tipo de cosas podría solucionarse mediante una mejor inferencia de tipos.

Otra pregunta es qué debería suceder si dice comenzar con un método ordinario (no asíncrono). Obviamente, un error de compilación sería la opción segura. Pero hay otras posibilidades.

Daniel Earwicker
fuente
Esto podría ser posible con una conversión implícita, pero de lo contrario requeriría una evaluación de izquierda a derecha de la declaración (que es exactamente lo contrario de cómo funciona normalmente el compilador, excepto lambdas). Creo que aún estaría en contra de esto porque evita el uso de var, potencialmente teniendo que sustituir algún nombre de tipo largo y explícito, y también es ambiguo entre el awaitcaso y el caso en el que alguien accidentalmente llamó al método asíncrono en lugar del método sincrónico normal. Parece intuitivo al principio, pero en realidad viola el principio de la menor sorpresa.
Aaronaught
@Aaronaught - ¿Por qué evita el uso de var? Me pregunto si estás respondiendo a la revisión anterior de mi respuesta ... Lo he reescrito completamente. Ahora puede pensar en esta sugerencia de la siguiente manera: si el método está marcado con un atributo especial, es como si la awaitpalabra clave se inserta automáticamente delante de las llamadas a ese método (a menos que suprima esto con el startprefijo). Todo permanece exactamente como en el CTP y, por lo tanto, varfunciona bien.
Daniel Earwicker
De hecho, era ... extraño que decidiera volver a visitar este hilo y responder a su respuesta casi exactamente al mismo tiempo que decidió editarlo. Tendré que releerlo ahora ...
Aaronaught
1
Me gusta su inversión de la palabra clave esperar. Y tampoco me gusta la triple redundancia de public async Task<int> FooAsync().
Allon Guralnek el
1
Sí, veo esa convención de nomenclatura Async-postfix como una señal de que algo podría capturarse de manera más formal. Básicamente, si hay una regla que dice "los métodos como este deberían llamarse de cierta manera, para que las personas sepan cómo llamarlos correctamente", entonces se deduce que la misma regla podría usarse para atribuir esos métodos de una manera determinada, y luego el compilador a ayudarlo a llamarlos correctamente.
Daniel Earwicker
4
hearken unto AsyncFetch(…)

(si no lo entiendes, lee la entrada del blog de Eric . Al menos es mejor que for sooth Romeo wherefore art thou AsyncFetch(…))

Nota para uno mismo - piense en un nombre
fuente
(aquellos sin sentido del humor no necesitan aplicar)
Nota para uno mismo - piense en un nombre
4

Creo que asyncestá bien, pero tal vez sea porque lo asocio con las páginas asincrónicas de ASP.NET, la misma idea.

Para la awaitpalabra clave prefiero continue aftero resume after.

No me gusta yieldni ninguna de sus variantes, porque la semántica es tal que el método nunca puede generar ejecución; Depende del estado de la tarea.

Aaronaught
fuente
Me gusta resume afterpara await. Tal vez asyncpodría ser llamado resumable.
Tim Goodman el
Aunque preferiría simplemente after, el continue afterenfoque tiene una gran ventaja de implementación: incluye una palabra clave contextual actualmente existente, pero con una sintaxis que no es compatible con el uso actual. Eso garantiza que la adición nunca romperá el código existente. Cuando se usa una palabra clave completamente nueva, la implementación debe hacer frente a los posibles usos de la palabra como un identificador en el código anterior, lo que puede ser bastante complicado.
Edurne Pascual
3

También agregué comentarios en el blog de Eric, no veo ningún problema con el uso de la misma palabra clave async

var data = async DownloadFileAsync(url);

Solo estoy expresando que quiero descargar el archivo de forma asincrónica. Aquí hay un poco de redundancia, "async" aparece dos veces, porque también está en el nombre del método. El compilador podría ser más inteligente y detectar la convención de que los métodos que terminan en "Asíncrono" son métodos asíncronos de hecho, y agregarlo para nosotros en el código compilado. Entonces, en vez de eso, solo querrás llamar

var data = async DownloadFile(url);

en lugar de llamar al síncrono

var data = DownloadFile(url);

Diablos, también deberíamos poder definirlos de la misma manera, ya que la palabra clave asincrónica está allí en nuestra declaración, ¿por qué debemos agregar manualmente "Async" a cada nombre de método? El compilador puede hacerlo por nosotros.

Mark H
fuente
Me gusta el azúcar que agregaste, aunque los chicos de C # probablemente no lo hagan. (Fwiw, que ya hacen algo similar en la búsqueda de nombres de atributos)
Nota a la libre - pensar en un nombre
2
Y dado que la asyncpalabra clave en el método es simplemente agradable (si lo he entendido correctamente), me pregunto si lo mejor no sería hacer lo contrario de lo que sugiere: abandonar asyncel método y simplemente usarlo donde tienen actualmente await.
Benjol
Esa es una posibilidad. La palabra clave asíncrona en la delimitación del método está ahí solo para la limpieza realmente. Preferiría que se mantuviera allí, pero sin el requisito de que agreguemos "Async" a los nombres de los métodos. Por ejemplo, en async Task<Byte[]> DownloadFile(...)lugar de Task<Byte[]> DownloadFileAsync(...)(este último será la firma compilada de todos modos). De cualquier manera funciona.
Mark H
Sinceramente, tampoco soy fanático de esto. Como en un comentario anterior, debo señalar que su versión final viola el principio de menor sorpresa, ya que en realidad está invocando un método completamente diferente del escrito, y la implementación y firma de ese método depende totalmente de la clase implementadora . Incluso la primera versión es problemática porque en realidad no dice nada (¿ejecutar asincrónicamente este método asincrónico?). El pensamiento que estamos tratando de expresar es una continuación o ejecución diferida y esto no expresa eso en absoluto.
Aaronaught
3

async = task : está modificando una función para devolver una tarea, entonces, ¿por qué no usar la palabra clave "task"?

await = terminar - No necesariamente tenemos que esperar, pero la tarea tiene que "terminar" antes de usar el resultado.

John Fisher
fuente
Es realmente difícil discutir con la simplicidad aquí.
sblom
2

Me gusta yield until. yield while, ya sugerido, es excelente y no introduce palabras clave nuevas, pero creo que "hasta" captura el comportamiento un poco mejor.

Creo que yield <something>es una gran idea, porque el rendimiento ya captura la idea de hacer que el resto del método sea una continuación tan bien. Quizás alguien pueda pensar en una palabra mejor que "hasta".

nlawalker
fuente
2

Solo quiero registrar mi voto para la sugerencia de Aaron G del comefromprimer uso apropiado que he visto de la declaración COMEFROM de INTERCAL . La idea es que es algo opuesto a GOTO (saltando de la declaración GOTO) en que hace que algún lugar en su código salte a la declaración COMEFROM.

Gabe
fuente
2

Como estamos tratando con Task<T>s, ¿qué tal si usamos startcomo palabra clave antes de la declaración, como en:

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

Protagonista
fuente
Hmmm, tal vez finishsería incluso mejor que start?
Protagonista el
1

Vale la pena señalar que F # también usa la asyncpalabra clave en sus flujos de trabajo asíncronos, que es casi exactamente lo mismo que la nueva funcionalidad asíncrona en C # 5. Por lo tanto, lo mantendría igual

Para la awaitpalabra clave en F #, solo usan en let!lugar de let. C # no tiene la misma sintaxis de asignación, por lo que necesitan algo en el lado derecho del =signo. Como dijo Benjol, funciona igual yieldque casi debería ser una variante de eso.

Scott Whitlock
fuente
1
Un "esperar" no necesita estar en una tarea (aunque, por supuesto, normalmente lo es). Es legal como operador en casi cualquier expresión que tenga un tipo en el que podamos encontrar un GetAwaiter. (Las reglas exactas aún no se han trabajado en una forma publicable.)
Eric Lippert
1
@Eric, en F # sería do!, pero sabías que ...
Benjol
1

yield async FetchAsync(..)


Esto va perfectamente con el asyncmodificador que necesita para poner el método que está invocando. Y también la semántica de la corriente yield return, es decir, devuelve y produce la ejecución del código de enumeración, mientras que en este caso está cediendo su ejecución al método asincrónico.

Imagínese si en el futuro habrá otros usos para yield, podríamos agregar un yield xdonde x es la nueva característica brillante en lugar de tener todas estas palabras clave diferentes para hacer principalmente la misma cosa, rendimiento de ejecución.

Francamente, no entiendo muy bien el argumento de 'no ceder ejecución'. Después de todo, ¿el punto de llamar a otro método ya no es 'ceder la ejecución' de ese método? ¿Independientemente de si es asíncrono o no? ¿Me estoy perdiendo algo aquí?

Y es bueno para usted si el asyncretorno es sincrónico, pero tener la palabra clave allí debería significar que existe una posibilidad probable de que el método se ejecute de forma asincrónica y que esté ejecutando la ejecución a otro método. Su método debe tener en cuenta eso independientemente de si el método realmente hace llamadas asincrónicas o no.

En mi opinión, los diversos casos de 'no rendir' son un detalle de implementación. Prefiero garantizar la coherencia en el lenguaje (es decir, reutilizar yield).

chakrit
fuente
0

¿Qué completetal, como en "Quiero completar la tarea"?

Task<byte[]> downloadTask = DownloadFileAsync(url);
byte[] data = complete downloadTask;
Allon Guralnek
fuente
1
¿Por qué el voto negativo? Es una cortesía al menos explicarte después de votar.
Allon Guralnek