[C#] Práctica de programación paralela: conceptos básicos de la programación asincrónica basada en tareas (Parte 2)

        El Capítulo 8 presenta las prácticas y soluciones disponibles para la programación asincrónica en C# y también analiza cuándo es apropiado utilizar la programación asincrónica. Este capítulo presenta principalmente las palabras clave async y await.

        De hecho, en estudios anteriores, todo el mundo ya entendió estas dos palabras clave y las utilizó mucho. De hecho, creo que no es necesario entrar en detalles, pero echemos un breve vistazo aquí.

        Aprenda Ingeniería con este Tutorial: Magician Dix/HandsOnParallelProgramming · GitCode

        Debido a limitaciones de espacio, este es el siguiente artículo. El contenido principal es el manejo de excepciones de código asincrónico y algunas precauciones al usar código asincrónico.


3. Manejo de excepciones de código asincrónico

        Con el código síncrono, todas las excepciones se propagan a la parte superior de la pila hasta que son manejadas por un bloque try-catch o se generan como excepciones no controladas.

        Cuando se utiliza un método asincrónico, la pila de llamadas es diferente porque el subproceso primero pasa del método al grupo de subprocesos y luego regresa al grupo de subprocesos.

        A continuación, usaremos ejemplos para comprender el rendimiento de las excepciones del programa:

        public static async Task ExceptionFunction()
        {
            Debug.Log($"开始执行异常方法!");
            await Task.Delay(1500);
            int x = 5;
            Debug.Log($"准备抛出异常!");
            Debug.Log(x / 0);
        }

        Aquí escribimos un método de error, que al final debería causar un error, aquí usamos directamente Task.Run para ejecutar:

Task.Run(ExceptionFunction);

        El resultado es obvio, no habrá ninguna impresión anormal, el código se rompe directamente en el lugar equivocado, sin seguir:

         Usar Thread para llamar al código anterior es lo mismo y no se generará ninguna excepción.

3.1 Crear tareas fuera de try-catch

        El código de muestra es el siguiente:

        private void RunWithExceptionFuntion()
        {
            Task task = Task.Run(ExceptionFunction);
            try
            {
                task.ContinueWith(x =>
                {
                    Debug.Log(x.IsFaulted);
                });
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.InnerException);//这样不会抛出异常
            }
        }

        No se generará ninguna excepción si se escribe así:

         No hay conocimiento de este error en try-catch, como si nunca hubiera sucedido. 

3.2 Crear tareas dentro de try-catch

        Ahora modifiquemos el código y movamos la creación de la Tarea dentro de try-catch:

        private void RunWithExceptionFuntion()
        {
            try
            {
                Task task = Task.Run(TestFunction.ExceptionFunction);
                task.ContinueWith(x =>
                {
                    Debug.Log(x.IsFaulted);
                });
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.InnerException);
            }
        }

        La diferencia con el libro es que todavía no se lanza ninguna excepción y el resultado de la ejecución es el mismo que en 3.1. Este problema ya se mencionó en el Capítulo 2. La tarea debe transferirse nuevamente al hilo principal (Task.Wait) antes de que el error pueda informarse normalmente.

3.3 Utilice subprocesos secundarios para recopilar excepciones de subprocesos secundarios

        Sin embargo, llamar a Task.Wait directamente hará que el hilo de llamada se atasque. Si se llama desde el hilo principal, hará que el hilo principal se atasque esperando asincrónico. Obviamente, no queremos llamar al hilo en espera; de lo contrario, la asincronía no tendrá sentido.

        Entonces, la solución es: cuando necesitamos iniciar un subproceso secundario (Tarea B), primero inicie un subproceso (Tarea A) para ejecutar la Tarea B; luego la Tarea A espera a que se complete la Tarea B para recopilar errores:
 

        private void RunWithExceptionFuntion()
        {
            RunExceptionFuntionAsync();
        }

        private async void RunExceptionFuntionAsync()
        {
            try
            {
                Task task = Task.Run(TestFunction.ExceptionFunction);
                await task;
                Debug.Log(task.IsFaulted);
            }
            catch (Exception ex)
            {
                Debug.LogError("出错了!");
                Debug.LogError(ex.Message);
            }
        }

La forma sencilla de escribirlo es como se muestra arriba, para que los errores se puedan imprimir normalmente.

3.4 ¿Devolver void provocará que el programa falle?

        Hay un caso mencionado en el libro:

        private void RunWithCrashFunction()
        {
            Task task = TestFunction.ExceptionFunction();
            Debug.Log($"RunWithCrashFunction End {task}");
        }

        Si el código anterior devuelve void en lugar de Task, el programa fallará. Pero, de hecho, este problema no existe y no se puede compilar en absoluto en mi versión:

         Esta sintaxis está en ExceptionFunction. No se informará ningún error si el valor de retorno es nulo o Tarea, pero void y Task aún se distinguen estrictamente al llamar. Entonces, incluso si el valor de retorno es incorrecto, el IDE lo detectará automáticamente.

        Es posible que el libro sea una versión antigua de C#, pero no existe en la versión actual.

4. Utilice PLINQ para implementar asíncrono

        De hecho, el uso de PLINQ  se explicó en detalle en el Capítulo 4: Uso de PLINQ , por lo que no entraré en detalles aquí. No hay nada nuevo en este capítulo del libro, así que puedes omitirlo.

5. Mida el rendimiento del código asincrónico.

        Aquí este libro habla primero de un ejemplo:

        private async void RunWithWaitDebugAll()
        {
            int x = await Utils.WaitWithTask(1100);
            int y = await Utils.WaitWithTask(2100);
            int z = await Utils.WaitWithTask(1500);
            Debug.Log($"返回结果:{x + y + z}");
        }

        Un código como este, aunque no bloquea el hilo principal, es serial y la tarea se completa esperando que se ejecute otra a su vez. El libro recomienda las siguientes modificaciones para paralelizar tres tareas:

        private async void RunWithWaitDebugAll()
        {
            int[] ret = await Task.WhenAll(Utils.WaitWithTask(1000), Utils.WaitWithTask(1500), Utils.WaitWithTask(412));
            int sum = 0;
            foreach (var item in ret)
                sum += item;
            Debug.Log($"返回结果:{sum}");
        }

        El efecto de ejecución es muy obvio, tampoco bloquea el hilo principal, pero el tiempo de espera no es la suma de 3 Tareas, sino el valor máximo entre ellas. Obviamente, en muchos escenarios de uso, esta forma de escribir tiene ventajas. Por supuesto, si realmente necesita la serialización para garantizar la lógica en los negocios, aún puede usar la primera forma de escritura.

        En pocas palabras, las tareas pequeñas se envían de forma asincrónica y simultánea tanto como sea posible, y se utilizan otros métodos (como semáforos y eventos) para garantizar una sincronización lógica.

6. Directrices para el uso de código asincrónico

        En realidad, este capítulo no es la información seca, las reglas, etc. que imaginamos, sino que habla principalmente de los siguientes asuntos:

        Evite el uso de vacío asincrónico: en el capítulo anterior, vimos algunas desventajas de devolver vacío, como no poder esperar o regresar sin estado de tarea, etc. Entonces, básicamente, si puede regresar a la Tarea, simplemente regrese a la Tarea. En realidad, no hay nada que decir al respecto.

        Hay algunos problemas de interbloqueo en ASP.NET:

Método Task.ConfigureAwait (System.Threading.Tasks) | Microsoft Learn configura el camarero utilizado para esperar esta tarea. icono-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task.configureawait?view=netstandard-2.1

        Pero usar Task.Wait en Unity no tiene estos problemas.


7. Resumen de este capítulo

        Este capítulo se siente un poco aguado...

        No es más que discutir las dos palabras clave async y await, y luego plantear problemas como void y el valor de retorno de Task. Pero, de hecho, estos problemas se han tratado en los capítulos anteriores y se han escrito muchas pruebas de código...

        Finalmente, hay otro dicho: trate de evitar escribir código que requiera cambiar el contexto de la tarea y hágalo en paralelo si se puede hacer en paralelo. Por supuesto, esto debe basarse en las necesidades comerciales y practicarse en el trabajo real.

         Aprenda Ingeniería con este Tutorial: Magician Dix/HandsOnParallelProgramming · GitCode

Supongo que te gusta

Origin blog.csdn.net/cyf649669121/article/details/131845533
Recomendado
Clasificación