¿Cómo se convierte Task <int> en un int?

116

Tenemos este método:

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();

   Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

   // You can do work here that doesn't rely on the string from GetStringAsync.
   DoIndependentWork();

   string urlContents = await getStringTask;
   //The thing is that this returns an int to a method that has a return type of Task<int>
   return urlContents.Length;
}

¿Se produce una conversión implícita entre Task<int>y int? Si no es así, ¿qué está pasando? ¿Cómo se implementa para que funcione?

Hombre libre
fuente
1
Sigue leyendo . Supongo que el compilador se encarga de eso en función de la asyncpalabra clave.
D Stanley
1
@Freeman, mira esta gran explicación: stackoverflow.com/a/4047607/280758
qehgt

Respuestas:

171

¿Se produce una conversión implícita entre Task <> e int?

¡No! Esto es solo una parte de cómo funciona async/ await.

Cualquier método declarado como asyncdebe tener un tipo de retorno de:

  • void (evitar si es posible)
  • Task (ningún resultado más allá de la notificación de finalización / falla)
  • Task<T>(para un resultado lógico de tipo Tde forma asincrónica)

El compilador hace todo el ajuste apropiado. El punto es que está regresando de forma asincrónicaurlContents.Length ; no puede hacer que el método simplemente regrese int, ya que el método real regresará cuando llegue a la primera awaitexpresión que aún no se ha completado. Entonces, en cambio, devuelve un Task<int>que se completará cuando se complete el método asincrónico.

Tenga en cuenta que awaithace lo contrario: desenvuelve un Task<T>a un Tvalor, que es como funciona esta línea:

string urlContents = await getStringTask;

... pero, por supuesto, lo desenvuelve de forma asincrónica, mientras que con solo usarlo Resultse bloquearía hasta que se complete la tarea. ( awaitpuede desenvolver otros tipos que implementan el patrón de espera, pero Task<T>es el que probablemente usará con más frecuencia).

Este doble envoltorio / desenvolvimiento es lo que permite que async sea tan componible. Por ejemplo, podría escribir otro método asíncrono que llame al suyo y duplique el resultado:

public async Task<int> AccessTheWebAndDoubleAsync()
{
    var task = AccessTheWebAsync();
    int result = await task;
    return result * 2;
}

(O simplemente return await AccessTheWebAsync() * 2;por supuesto).

Jon Skeet
fuente
3
¿Se pueden ofrecer detalles sobre cómo funciona bajo el capó, solo por curiosidad?
Freeman
8
+1 Buena respuesta como siempre. ¡¿Y por qué los escribes tan rápido ?!
Felix K.
9
+1: Acabo de comenzar a buscar en async/ awaity encuentro esto extremadamente poco intuitivo. En mi opinión, debe haber una palabra clave o similar en el returnpara aclarar esto, por ejemplo return async result;(de la misma manera que await result"desenvuelve" el Tde Tast<T>).
dav_i
2
@JonSkeet Pero no tiene sentido sin el await- con T foo = someTaskT;lo que obtendría "No se puede convertir implícitamente el tipo Task<T>en T" - de la misma manera que yo sostengo que tendría más sentido tener una palabra clave para el inverso (envolviendo Task<T>). Estoy a favor de eliminar la pelusa, pero en este caso creo que proporciona una ofuscación innecesaria dentro de los asyncmétodos. (¡Obviamente el punto es discutible porque los poderes fácticos ya han hablado / codificado!)
dav_i
2
@dav_i: La tarea no tiene sentido, pero el resto sí. Y hay casos en los que toda la declaración tendría sentido, aunque podría no ser útil. Dado que el método ya está declarado async, creo que es suficiente.
Jon Skeet
18

No requiere convertir la tarea a int. Simplemente use el resultado de la tarea.

int taskResult = AccessTheWebAndDouble().Result;

public async Task<int> AccessTheWebAndDouble()
{
    int task = AccessTheWeb();
    return task;
}

Devolverá el valor si está disponible; de ​​lo contrario, devolverá 0.

Aniket Sharma
fuente
20
eso no es lo que pregunté.
Freeman
16
Esto no responde a la pregunta. Pero lo que es más importante, este es un muy mal consejo . Casi nunca debería usar Result; ¡Puede causar interbloqueos! Considere, por ejemplo, este flujo de trabajo: (1) Escriba una nota que diga "cortar el césped". (2) Espere a que se corte el césped (3) Coma un sándwich, (4) Haga lo que diga en la nota ". Con ese flujo de trabajo, nunca come un sándwich ni corta el césped, porque el paso 2 es una espera sincrónica en algo que harás en el futuro . Pero ese es el flujo de trabajo que estás describiendo aquí.
Eric Lippert
@EricLippert: No claro tu ejemplo. ¿Puede explicar cómo Result puede introducir puntos muertos cuando la espera no?
CharithJ
3
Esperar significa hacer algo mientras espera el resultado y ese algo puede incluir trabajar para calcular el resultado. Pero las esperas sincrónicas no hacen nada mientras espera, lo que significa que podría estar impidiendo que se realice el trabajo.
Eric Lippert
1
@EricLippert. ¿Tendrá esto el mismo problema? 'Task.Run (() => AccessTheWebAndDouble ()). Result;'
CharithJ