usar puede operadores mvc metodos metodo method example ejemplo delegados create carece await async asincronos asincrono c# multithreading asynchronous parallel-processing async-await

c# - puede - ¿Cuál es la diferencia entre programación asincrónica y multihilo?



no se puede usar await en void (2)

Javascript en el navegador es un gran ejemplo de un programa asincrónico que no tiene hilos.

No tiene que preocuparse de que varias partes del código toquen los mismos objetos al mismo tiempo: cada función terminará de ejecutarse antes de que cualquier otro javascript pueda ejecutarse en la página.

Sin embargo, al hacer algo como una solicitud AJAX, no se está ejecutando ningún código, por lo que otros javascript pueden responder a cosas como eventos de clic hasta que esa solicitud regrese e invoque la devolución de llamada asociada con ella. Si uno de estos otros controladores de eventos todavía se está ejecutando cuando vuelve la solicitud AJAX, no se llamará a su controlador hasta que finalice. Solo se está ejecutando un "hilo" de JavaScript, aunque es posible que pause efectivamente lo que estaba haciendo hasta que tenga la información que necesita.

En las aplicaciones de C #, sucede lo mismo cada vez que se trata de elementos de la interfaz de usuario: solo se le permite interactuar con los elementos de la interfaz de usuario cuando está en el hilo de la interfaz de usuario. Si el usuario hizo clic en un botón y desea responder leyendo un archivo grande del disco, un programador inexperto podría cometer el error de leer el archivo dentro del controlador de eventos de clic, lo que haría que la aplicación se "congelara" hasta que el archivo terminó de cargarse porque no está permitido responder a más clics, desplazamientos o cualquier otro evento relacionado con la interfaz de usuario hasta que se libere ese hilo.

Una opción que los programadores podrían usar para evitar este problema es crear un nuevo hilo para cargar el archivo, y luego decirle al código de ese hilo que cuando se carga el archivo necesita ejecutar el código restante en el hilo de la interfaz de usuario nuevamente para que pueda actualizar los elementos de la interfaz de usuario basado en lo que encontró en el archivo. Hasta hace poco, este enfoque era muy popular porque era lo que las bibliotecas y el lenguaje de C # facilitaban, pero es fundamentalmente más complicado de lo que tiene que ser.

Si piensa en lo que está haciendo la CPU cuando lee un archivo a nivel de hardware / sistema operativo, básicamente emite una instrucción para leer datos del disco en la memoria y golpear el sistema operativo con una "interrupción" cuando La lectura está completa. En otras palabras, leer desde el disco (o cualquier E / S realmente) es una operación inherentemente asíncrona . El concepto de un hilo que espera que se complete esa E / S es una abstracción que los desarrolladores de la biblioteca crearon para facilitar la programación. No es necesario.

Ahora, la mayoría de las operaciones de E / S en .NET tienen un método ...Async() que puede invocar, que devuelve una Task casi de inmediato. Puede agregar devoluciones de llamada a esta Task para especificar el código que desea ejecutar cuando se complete la operación asincrónica. También puede especificar en qué hilo desea que se ejecute ese código, y puede proporcionar un token que la operación asincrónica puede verificar de vez en cuando para ver si decidió cancelar la tarea asincrónica, dándole la oportunidad de detener su trabajo rápidamente Y con gracia.

Hasta que se agregaron las palabras clave async/await , C # era mucho más obvio sobre cómo se invoca el código de devolución de llamada, porque esas devoluciones de llamada eran en forma de delegados que usted asoció con la tarea. Para poder seguir brindándole la ventaja de utilizar la operación ...Async() , evitando la complejidad del código, async/await la creación de esos delegados. Pero todavía están allí en el código compilado.

Por lo tanto, puede hacer que su controlador de eventos de IU await una operación de E / S, liberando el hilo de la interfaz de usuario para hacer otras cosas y volviendo más o menos automáticamente al hilo de la interfaz de usuario una vez que haya terminado de leer el archivo, sin tener que para crear un nuevo hilo.

Pensé que eran básicamente lo mismo: escribir programas que dividieran las tareas entre procesadores (en máquinas que tienen más de 2 procesadores). Entonces estoy leyendo https://msdn.microsoft.com/en-us/library/hh191443.aspx , que dice:

Los métodos asíncronos están destinados a ser operaciones sin bloqueo. Una expresión de espera en un método asíncrono no bloquea el hilo actual mientras se ejecuta la tarea esperada. En cambio, la expresión registra el resto del método como una continuación y devuelve el control al llamador del método asíncrono.

Las palabras clave asíncronas y en espera no hacen que se creen hilos adicionales. Los métodos asincrónicos no requieren subprocesos múltiples porque un método asincrónico no se ejecuta en su propio hilo. El método se ejecuta en el contexto de sincronización actual y usa el tiempo en el hilo solo cuando el método está activo. Puede usar Task.Run para mover el trabajo vinculado a la CPU a un subproceso en segundo plano, pero un subproceso en segundo plano no ayuda con un proceso que solo está esperando que los resultados estén disponibles.

y me pregunto si alguien puede traducir eso al inglés para mí. Parece establecer una distinción entre asincronicidad (¿es eso una palabra?) Y subprocesos e implica que puede tener un programa que tiene tareas asincrónicas pero no subprocesamiento múltiple.

Ahora entiendo la idea de tareas asincrónicas como el ejemplo en la pág. 467 de Jon Skeet''s C # In Depth, Third Edition

async void DisplayWebsiteLength ( object sender, EventArgs e ) { label.Text = "Fetching ..."; using ( HttpClient client = new HttpClient() ) { Task<string> task = client.GetStringAsync("http://csharpindepth.com"); string text = await task; label.Text = text.Length.ToString(); } }

La palabra clave async significa " Esta función, siempre que se llame, no se llamará en un contexto en el que se requiera su finalización para todo después de que se llame su llamada".

En otras palabras, escribirlo en medio de alguna tarea

int x = 5; DisplayWebsiteLength(); double y = Math.Pow((double)x,2000.0);

, dado que DisplayWebsiteLength() no tiene nada que ver con x o y , hará que DisplayWebsiteLength() se ejecute "en segundo plano", como

processor 1 | processor 2 ------------------------------------------------------------------- int x = 5; | DisplayWebsiteLength() double y = Math.Pow((double)x,2000.0); |

Obviamente ese es un ejemplo estúpido, pero ¿estoy en lo correcto o estoy totalmente confundido o qué?

(Además, estoy confundido acerca de por qué el sender y e nunca se utilizan en el cuerpo de la función anterior).


Tu malentendido es extremadamente común. A muchas personas se les enseña que los subprocesos múltiples y la asincronía son lo mismo, pero no lo son.

Una analogía generalmente ayuda. Estás cocinando en un restaurante. Se recibe un pedido de huevos y tostadas.

  • Sincrónico: cocinas los huevos, luego cocinas las tostadas.
  • Asíncrono, de un solo hilo: comienza a cocinar los huevos y establece un temporizador. Empiezas a cocinar las tostadas y configuras un temporizador. Mientras ambos están cocinando, tú limpias la cocina. Cuando se apagan los temporizadores, retira los huevos del fuego y las tostadas de la tostadora y sírvelas.
  • Asíncrono, multiproceso: contrata a dos cocineros más, uno para cocinar huevos y otro para cocinar tostadas. Ahora tiene el problema de coordinar a los cocineros para que no entren en conflicto en la cocina cuando comparten recursos. Y tienes que pagarles.

¿Tiene sentido ahora que el subprocesamiento múltiple es solo un tipo de asincronía? Enhebrar es sobre trabajadores; La asincronía se trata de tareas . En los flujos de trabajo multiproceso, asigna tareas a los trabajadores. En los flujos de trabajo asincrónicos de subproceso único, tiene un gráfico de tareas donde algunas tareas dependen de los resultados de otras; a medida que cada tarea se completa, invoca el código que programa la próxima tarea que puede ejecutarse, dados los resultados de la tarea que se acaba de completar. Pero (con suerte) solo necesita un trabajador para realizar todas las tareas, no un trabajador por tarea.

Ayudará a darse cuenta de que muchas tareas no están vinculadas al procesador. Para las tareas vinculadas al procesador, tiene sentido contratar tantos trabajadores (subprocesos) como procesadores, asignar una tarea a cada trabajador, asignar un procesador a cada trabajador y hacer que cada procesador haga el trabajo de nada más que calcular el resultado como lo mas rapido posible. Pero para las tareas que no están esperando en un procesador, no necesita asignar un trabajador en absoluto. Simplemente espere a que llegue el mensaje de que el resultado está disponible y haga otra cosa mientras espera . Cuando llegue ese mensaje, puede programar la continuación de la tarea completada como lo siguiente en su lista de tareas pendientes para marcar.

Así que veamos el ejemplo de Jon con más detalle. ¿Lo que pasa?

  • Alguien invoca DisplayWebSiteLength. ¿Quien? No nos importa
  • Establece una etiqueta, crea un cliente y le pide al cliente que busque algo. El cliente devuelve un objeto que representa la tarea de buscar algo. Esa tarea está en progreso.
  • ¿Está en progreso en otro hilo? Probablemente no. Lea blog.stephencleary.com/2013/11/there-is-no-thread.html sobre por qué no hay hilo.
  • Ahora esperamos la tarea. ¿Lo que pasa? Verificamos si la tarea se ha completado entre el momento en que la creamos y la esperamos. En caso afirmativo, buscamos el resultado y seguimos corriendo. Supongamos que no se ha completado. Suscribimos el resto de este método como la continuación de esa tarea y regresamos .
  • Ahora el control ha vuelto a la persona que llama. ¿Qué hace? Lo que quiera
  • Ahora suponga que la tarea se completa. ¿Cómo hizo eso? Tal vez se estaba ejecutando en otro subproceso, o tal vez la persona que llamó a la que acabamos de regresar permitió que se ejecutara en el subproceso actual. De todos modos, ahora tenemos una tarea completada.
  • La tarea completada le pide al hilo correcto, nuevamente, probablemente el único hilo, que ejecute la continuación de la tarea.
  • El control pasa inmediatamente al método que acabamos de dejar en el punto de espera. Ahora hay un resultado disponible para que podamos asignar text y ejecutar el resto del método.

Es como en mi analogía. Alguien te pide un documento. Envías por correo el documento y sigues haciendo otro trabajo. Cuando llegue por correo, se le indicará, y cuando lo desee, hará el resto del flujo de trabajo: abra el sobre, pague los gastos de envío, lo que sea. No necesita contratar a otro trabajador para que haga todo eso por usted.