recursividad primo numeros numero invertir financiera ejercicios con basicos algoritmos c# recursion async-await

c# - primo - recursividad financiera



Recursión y la espera/asincronización (2)

En su primer y segundo ejemplo, TestAsync todavía está esperando que la llamada se devuelva. La diferencia es que la recursión es imprimir y devolver el hilo a otro trabajo en el segundo método. Por lo tanto, la recursión no es lo suficientemente rápida como para ser un desbordamiento de pila. Sin embargo, la primera tarea aún está esperando y, finalmente, el recuento alcanzará su tamaño entero máximo o se volverá a generar desbordamiento de la pila. El punto es que se devuelve el hilo de llamada pero el método asíncrono real está programado en el mismo hilo. Básicamente, el método TestAsync se olvida hasta que se complete la espera, pero aún se mantiene en la memoria. Se permite que el subproceso haga otras cosas hasta que finalice y luego se recuerda ese subproceso y finaliza donde lo dejó. Las llamadas de espera adicionales almacenan el hilo y lo olvidan nuevamente hasta que se complete nuevamente. Hasta que se completen todos los tiempos de espera y el método se complete, TaskAsync todavía está en la memoria. Entonces, aquí está la cosa. Si digo un método para hacer algo y luego llamo, aguarde una tarea. El resto de mis códigos en otros lugares continúan en ejecución. Cuando se completa la espera, el código vuelve a aparecer y finaliza y luego vuelve a lo que estaba haciendo en ese momento justo antes. En los ejemplos, su TaskAsync siempre está en un estado desechado (por así decirlo) hasta que se complete la última llamada y devuelva las llamadas a la cadena.

EDITAR: Sigo diciendo que guardo el hilo o el hilo y me refiero a la rutina. Todos están en el mismo hilo, que es el hilo principal en su ejemplo. Lo siento si te confundí.

Tengo una comprensión frágil de cómo funciona la palabra clave await, y quiero ampliar mi comprensión un poco.

El problema que todavía me da vueltas es el uso de la recursividad. Aquí hay un ejemplo:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestingAwaitOverflow { class Program { static void Main(string[] args) { var task = TestAsync(0); System.Threading.Thread.Sleep(100000); } static async Task TestAsync(int count) { Console.WriteLine(count); await TestAsync(count + 1); } } }

Obviamente, esta arroja una StackOverflowException .

Mi comprensión es porque el código realmente se ejecuta sincrónicamente hasta la primera acción asíncrona, después de lo cual devuelve un objeto Task que contiene información sobre la operación asincrónica. En este caso, no hay una operación asincrónica, por lo que solo se repiten bajo la falsa promesa de que eventualmente obtendrá una Task devuelta.

Ahora cambiándolo solo un poquito:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestingAwaitOverflow { class Program { static void Main(string[] args) { var task = TestAsync(0); System.Threading.Thread.Sleep(100000); } static async Task TestAsync(int count) { await Task.Run(() => Console.WriteLine(count)); await TestAsync(count + 1); } } }

Este no arroja una StackOverflowException . Puedo ver por qué funciona, pero lo llamaría una corazonada (probablemente se trata de cómo el código está dispuesto para utilizar devoluciones de llamada para evitar la construcción de la pila, pero no puedo traducir esa sensación instintiva en una explicación)

Entonces tengo dos preguntas:

  • ¿Cómo el segundo lote de código evita una StackOverflowException ?
  • ¿El segundo lote de código desperdicia otros recursos? (por ejemplo, ¿asigna un número absurdamente grande de objetos Tarea en el montón?)

¡Gracias!


La parte hasta la primera espera en cualquier función se ejecuta sincrónicamente. En el primer caso se topa con un desbordamiento de pila debido a eso, no hay nada que interrumpa la función que se llama a sí mismo.

La primera espera (que no se completa de inmediato - este es el caso para usted con probabilidad alta) hace que la función regrese (¡y renuncie a su espacio de pila!). Pone en cola el resto como una continuación. El TPL asegura que las continuaciones nunca anidan demasiado profundamente. Si existe un riesgo de desbordamiento de pila, la continuación se pone en cola para el grupo de subprocesos, restableciendo la pila (que comenzaba a llenarse).

¡El segundo ejemplo aún puede desbordarse! ¿Qué pasa si la tarea Task.Run siempre se completa inmediatamente? (Esto es poco probable pero posible con la programación correcta de subprocesos del sistema operativo). Entonces, la función asíncrona nunca se interrumpirá (haciendo que regrese y libere todo el espacio de la pila) y tendrá el mismo comportamiento que en el caso 1.