Saltar al contenido

En Unity / C #, ¿el async / await de .Net comienza, literalmente, otro hilo?

Por fin luego de tanto trabajar pudimos dar con el resultado de esta incógnita que ciertos usuarios de nuestro espacio han presentado. Si tienes algún dato que aportar no dudes en dejar tu conocimiento.

Solución:

Esta lectura: Las tareas (todavía) no son subprocesos y la asincrónica no es paralela puede ayudarlo a comprender lo que está sucediendo bajo el capó. En resumen, para que su tarea se ejecute en un hilo separado, debe llamar

Task.Run(()=>// the work to be done on a separate thread. ); 

Entonces puede esperar esa tarea donde sea necesario.

Para responder tu pregunta

“De hecho, ¿el código de arriba lanza literalmente otro hilo, o c # /. Net usa algún otro enfoque para lograr tareas cuando usas el patrón natty async / wait?”

No, no es así.

Si lo hiciste

await Task.Run(()=> cws.ConnectAsync(u, CancellationToken.None));

Luego cws.ConnectAsync(u, CancellationToken.None) se ejecutaría en un hilo separado.

Como respuesta al comentario aquí está el código modificado con más explicaciones:

    async void SendExplosionInfo() 

        cws = new ClientWebSocket();
        try 
            var myConnectTask = Task.Run(()=>cws.ConnectAsync(u, CancellationToken.None));

            // more code running...
await myConnectTask; // here's where it will actually stop to wait for the completion of your task. 
            Scene.NewsFromServer("done!"); // class function to go back to main tread
        
        catch (Exception e)  ... 
    

Sin embargo, es posible que no lo necesite en un hilo separado porque el trabajo asíncrono que está haciendo no está vinculado a la CPU (o eso parece). Por lo tanto, debería estar bien con

 try 
            var myConnectTask =cws.ConnectAsync(u, CancellationToken.None);

            // more code running...
await myConnectTask; // here's where it will actually stop to wait for the completion of your task. 
            Scene.NewsFromServer("done!"); // continue from here
        
        catch (Exception e)  ... 
    }

Secuencialmente hará exactamente lo mismo que el código anterior pero en el mismo hilo. Permitirá el código después de “ConnectAsync“para ejecutar y solo se detendrá para esperar a que se complete”ConnectAsync” En donde dice esperar y desde “ConnectAsync“no está vinculado a la CPU, usted (lo que lo hace algo paralelo en el sentido del trabajo que se realiza en otro lugar, es decir, la red) tendrá suficiente energía para ejecutar sus tareas, a menos que su código esté en “….” también requiere mucho trabajo vinculado a la CPU, que preferiría ejecutar en paralelo.

También es posible que desee evitar el uso asincrónico void porque está ahí solo para funciones de nivel superior. Intenta usar Tarea asincrónica en la firma de su método. Puedes leer más sobre esto aquí.

No, async / await no significa – otro hilo. Puede iniciar otro hilo, pero no es necesario.

Aquí puede encontrar una publicación bastante interesante al respecto: https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/

Noticia importante

En primer lugar, hay un problema con la primera declaración de su pregunta.

La unidad es de un solo subproceso

La unidad es no de un solo hilo; de hecho, Unity es un multiproceso medio ambiente. ¿Por qué? Simplemente vaya a la página web oficial de Unity y lea allí:

Sistema multiproceso de alto rendimiento: Utilice al máximo los procesadores multinúcleo disponibles hoy (y mañana), sin una programación pesada. Nuestra nueva base para permitir el alto rendimiento se compone de tres subsistemas: el sistema de trabajo C #, que le brinda una caja de arena segura y fácil para escribir código paralelo; Entity Component System (ECS), un modelo para escribir código de alto rendimiento por defecto, y Burst Compiler, que produce código nativo altamente optimizado.

El motor Unity 3D utiliza un tiempo de ejecución .NET llamado “Mono” que es multiproceso por su naturaleza. Para algunas plataformas, el código administrado se transformará en código nativo, por lo que no habrá tiempo de ejecución .NET. Pero el código en sí será multiproceso de todos modos.

Así que, por favor, no diga hechos engañosos y técnicamente incorrectos.

Lo que estás discutiendo es simplemente una afirmación de que hay un hilo principal en Unity que procesa la carga de trabajo central de una manera basada en marcos. Este es true. ¡Pero no es algo nuevo y único! Por ejemplo, una aplicación WPF que se ejecuta en .NET Framework (o .NET Core a partir de 3.0) también tiene un hilo principal (a menudo llamado Hilo de interfaz de usuario), y la carga de trabajo se procesa en ese subproceso de una manera basada en marcos utilizando WPF Dispatcher (cola de despachador, operaciones, marcos, etc.) ¡Pero todo esto no hace que el entorno sea de un solo subproceso! Es solo una forma de manejar la lógica de la aplicación.


Una respuesta a tu pregunta

Tenga en cuenta: mi respuesta solo se aplica a las instancias de Unity que ejecutan un entorno de tiempo de ejecución .NET (Mono). Para aquellas instancias que convierten el código C # administrado en código C ++ nativo y compilan / ejecutan binarios nativos, mi respuesta probablemente sea al menos inexacta.

Usted escribe:

Cuando haces algo en otro hilo, a menudo usas async / wait ya que, eh, todos los buenos programadores de C # dicen que esa es la manera fácil de hacerlo.

los async y await Las palabras clave en C # son solo una forma de usar el TAP (patrón asincrónico de tareas).

El TAP se utiliza para arbitrario operaciones asincrónicas. Generalmente hablando, no hay hilo. Recomiendo encarecidamente leer este artículo de Stephen Cleary llamado “No hay hilo”. (Stephen Cleary es un reconocido gurú de la programación asincrónica si no lo sabe).

La causa principal para usar el async/await característica es una operación asincrónica. Tu usas async/await no porque “hagas algo en otro hilo”, sino porque tienes una operación asincrónica por la que tienes que esperar. Si hay un hilo en segundo plano, esta operación se ejecutará o no, esto no le importa (bueno, casi; ver más abajo). El TAP es un nivel de abstracción que oculta estos detalles.

De hecho, ¿El código de arriba literalmente lanza otro hilo?, ¿o c # /. Net usa algún otro enfoque para lograr tareas cuando usa el patrón natty async / wait?

La respuesta correcta es: depende.

  • si ClientWebSocket.ConnectAsync lanza una excepción de validación de argumento de inmediato (por ejemplo, un ArgumentNullException cuando uri es null), no hay hilo nuevo se iniciará
  • si el código en ese método se completa muy rápidamente, el resultado del método estará disponible sincrónicamente, no hay hilo nuevo se iniciará
  • si la implementación de la ClientWebSocket.ConnectAsync El método es una operación asincrónica pura sin hilos involucrados, su llamada método será “suspendido” (debido a await) – asi que no hay hilo nuevo se iniciará
  • si la implementación del método involucra subprocesos y el actual TaskScheduler es capaz de programar este elemento de trabajo en un subproceso de grupo de subprocesos en ejecución, no hay hilo nuevo se iniciará; en su lugar, el elemento de trabajo se pondrá en cola en un ya se está ejecutando el grupo de subprocesos hilo
  • si todos los subprocesos del grupo de subprocesos ya están ocupados, el tiempo de ejecución puede generar nuevos subprocesos según su configuración y el estado actual del sistema, así que sí, un se podría iniciar un nuevo hilo y el elemento de trabajo se pondrá en cola en ese nuevo hilo

Verá, esto es bastante complejo. Pero esa es exactamente la razón por la que el patrón TAP y el async/await el par de palabras clave se introdujo en C #. Por lo general, estas son las cosas con las que un desarrollador no quiere molestarse, así que ocultemos estas cosas en el tiempo de ejecución / marco.

@agfc afirma algo que no es del todo correcto:

“Esto no ejecutará el método en un hilo en segundo plano”

await cws.ConnectAsync(u, CancellationToken.None);

“Pero esto lo hará”

await Task.Run(()=> cws.ConnectAsync(u, CancellationToken.None));

Si ConnectAsyncLa implementación de la parte síncrona es pequeña, el programador de tareas podría ejecutar esa parte sincrónicamente en ambos casos. Por lo tanto, estos dos fragmentos pueden ser exactamente iguales dependiendo de la implementación del método llamado.

Tenga en cuenta que el ConnectAsync tiene un Async sufijo y devuelve un Task. Esta es una información basada en convenciones de que el método es verdaderamente asincrónico. En tales casos, debe siempre preferir await MethodAsync() sobre await Task.Run(() => MethodAsync()).

Más lecturas interesantes:

  • await vs await Task.Run
  • return Task.Run vs await Task.Run

Calificaciones y comentarios

Finalizando este artículo puedes encontrar las críticas de otros administradores, tú asimismo eres capaz mostrar el tuyo si lo deseas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *