c# - No esperar una llamada asincrónica sigue siendo asincrónica, ¿verdad?
.net asynchronous (2)
Lo siento si esta es una pregunta tonta (o un duplicado).
Tengo una función
A
:
public async Task<int> A(/* some parameters */)
{
var result = await SomeOtherFuncAsync(/* some other parameters */);
return (result);
}
Tengo otra función
B
, que llama a
A
pero no usa el valor de retorno:
public Task B(/* some parameters */)
{
var taskA = A(/* parameters */); // #1
return (taskA);
}
Tenga en cuenta que
B
no se declara
async
y no está esperando la llamada a
A
La llamada a
A
no es una llamada de disparar y olvidar:
B
llama a
B
así:
public async Task C()
{
await B(/* parameters */);
}
Tenga en cuenta que en el n
. ° 1
, no hay que
await
.
Tengo un compañero de trabajo que afirma que esto hace que la llamada a
A
síncrona y sigue apareciendo con
Console.WriteLine
logs que aparentemente demuestran su punto.
Intenté señalar que solo porque no esperamos los resultados dentro de
B
,
se
espera la cadena de tareas y la naturaleza del código dentro de
A
no cambia solo porque no lo esperamos.
Como el valor de retorno de
A
no es necesario, no es necesario esperar la tarea en el sitio de la llamada, siempre y cuando
alguien de la cadena lo espere
(lo que sucede en
C
).
Mi compañero de trabajo es muy insistente y comencé a dudar de mí mismo. ¿Está mal mi entendimiento?
Lo siento si esta es una pregunta tonta
No es una pregunta tonta. Es una pregunta importante.
Tengo un compañero de trabajo que afirma que esto hace que la llamada a A sea síncrona y sigue apareciendo con Console.WriteLine logs que aparentemente demuestran su punto.
Ese es el problema fundamental allí mismo, y debe educar a su compañero de trabajo para que dejen de engañarse a sí mismos y a los demás. No hay tal cosa como una llamada asincrónica . La llamada no es lo que es asíncrono, nunca . Dilo conmigo. Las llamadas no son asíncronas en C # . En C #, cuando llama a una función, esa función se llama inmediatamente después de calcular todos los argumentos .
Si su compañero de trabajo o usted cree que existe una llamada asincrónica, se encontrará con un mundo de dolor porque sus creencias sobre cómo funciona la asincronía estarán muy desconectadas de la realidad.
Entonces, ¿es correcto tu compañero de trabajo?
Por supuesto que lo son.
La llamada a
A
es síncrona porque todas las llamadas a funciones son síncronas
.
Pero el hecho de que crean que existe una "llamada asincrónica" significa que están muy equivocados acerca de cómo funciona la asincronía en C #.
Si específicamente su compañero de trabajo cree que
await M()
alguna manera hace que la llamada a
M()
"asíncrona", entonces su compañero de trabajo tiene un gran malentendido.
await
es un
operador
.
Es un operador complicado, sin duda, pero es un operador y funciona con valores.
await M()
y
var t = M(); await t;
var t = M(); await t;
Son
lo mismo
.
La espera ocurre
después de
la llamada porque la
await
opera en el valor que se devuelve
.
await
NO
es una instrucción para el compilador de "generar una llamada asincrónica a M ()" o algo así;
no hay tal cosa como una "llamada asincrónica".
Si esa es la naturaleza de su falsa creencia, entonces tienes la oportunidad de educar a tu compañero de trabajo sobre lo
await
significa
await
.
await
significa algo simple pero poderoso.
Significa:
-
Mira la
Task
en la que estoy operando. - Si la tarea se completa excepcionalmente, arroje esa excepción
- Si la tarea se completa normalmente, extraiga ese valor y úselo
-
Si la tarea está incompleta, registre el resto de este método como la continuación de la tarea esperada y devuelva una
nueva
Task
represente el flujo de trabajo asincrónico incompleto de esta llamada a mi interlocutor .
Eso es todo lo que
await
.
Simplemente examina el contenido de una tarea, y si la tarea está incompleta, dice "bueno, no podemos avanzar en este flujo de trabajo hasta que se complete esa tarea, así que regrese a mi interlocutor que encontrará algo más para esta CPU que hacer".
La naturaleza del código dentro de A no cambia solo porque no lo esperamos.
Eso es correcto.
Llamamos sincrónicamente
A
, y devuelve una
Task
.
El código después del sitio de la llamada no se ejecuta hasta que regrese
A
Lo interesante de
A
es que
A
puede devolver una
Task
incompleta a su llamante
, y esa tarea representa
un nodo en un flujo de trabajo asincrónico
.
El flujo de trabajo
ya
es asíncrono y, como observa, no importa a
A
lo que haga con su valor de retorno
después de
que regrese;
A
no tiene idea de si va a
await
la
Task
devuelta o no.
A
ejecuta todo el tiempo que puede, y luego devuelve una tarea completada normalmente, o una tarea completada excepcionalmente, o devuelve una tarea incompleta.
Pero nada de lo que haga en el sitio de llamadas cambia eso.
Como el valor de retorno de A no es necesario, no es necesario esperar la tarea en el sitio de la llamada
Correcto.
no hay necesidad de esperar la tarea en el sitio de la llamada, siempre y cuando alguien de la cadena lo espere (lo que sucede en C).
Ahora me has perdido.
¿Por qué alguien
tiene
que esperar la
Task
devuelta por
A
?
Diga por qué cree que se
requiere
que alguien
await
esa
Task
, porque podría tener una creencia falsa.
Mi compañero de trabajo es muy insistente y comencé a dudar de mí mismo. ¿Está mal mi entendimiento?
Su compañero de trabajo es casi seguro que está equivocado.
Su análisis parece correcto hasta el punto en que dice que hay un
requisito de
que se
await
cada
Task
, lo cual no es cierto.
Es
extraño
no
await
una
Task
porque significa que escribiste un programa donde comenzaste una operación y no te importa cuándo o cómo se completa, y ciertamente
huele mal
escribir un programa como ese, pero no hay un
requisito
para
await
cada
Task
.
Si crees que existe, de nuevo, di cuál es esa creencia y lo resolveremos.
Tienes razón.
Crear una tarea solo hace eso y no le importa cuándo y quién esperará su resultado.
Intenta poner a la
await Task.Delay(veryBigNumber);
en
SomeOtherFuncAsync
y la salida de la consola debería ser lo que esperarías.
Esto se llama elidir y le sugiero que lea esta publicación de blog, donde puede ver por qué debería o no hacer tal cosa.
También algún ejemplo mínimo (poco complicado) copiando su código demostrando que tiene razón:
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine($"Start of main {Thread.CurrentThread.ManagedThreadId}");
var task = First();
Console.WriteLine($"Middle of main {Thread.CurrentThread.ManagedThreadId}");
await task;
Console.WriteLine($"End of main {Thread.CurrentThread.ManagedThreadId}");
}
static Task First()
{
return SecondAsync();
}
static async Task SecondAsync()
{
await ThirdAsync();
}
static async Task ThirdAsync()
{
Console.WriteLine($"Start of third {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(1000);
Console.WriteLine($"End of third {Thread.CurrentThread.ManagedThreadId}");
}
}
Esto escribe
Middle of main
antes de
End of third
, demostrando que de hecho es asíncrono.
Además, puede (muy probablemente) ver que los extremos de las funciones se ejecutan en un hilo diferente que el resto del programa.
Tanto los inicios como el medio de main siempre se ejecutarán en el mismo subproceso porque de hecho son sincrónicos (main comienza, llama a la cadena de funciones, terceros regresa (puede regresar en la línea con la palabra clave
await
) y luego main continúa como si hubiera nunca estuvo involucrada una función asincrónica. Las terminaciones después de las palabras clave en
await
en ambas funciones pueden ejecutarse en cualquier subproceso en el ThreadPool (o en el contexto de sincronización que está utilizando).
Ahora es interesante notar que si
Task.Delay
en
Third
no tomó mucho tiempo y realmente terminó sincrónicamente, todo esto se ejecutaría en un solo hilo.
Lo que es más, a pesar de que se ejecutaría de forma asincrónica,
podría
ejecutarse en un solo hilo.
No existe una regla que establezca que una función asíncrona usará más de un hilo, es muy posible que solo haga algún otro trabajo mientras espera que termine alguna tarea de E / S.