without understanding run net method example ejemplos create await async c# .net async-await stack-overflow

understanding - task run async c#



StackOverflowExceptions en métodos anync anidados al desenrollar la pila (1)

¿Por qué la pila crece en el camino de desenrollado (en comparación con un método que no provoca un cambio de hilo en espera)?

La razón principal es porque await programa sus continuaciones con el indicador TaskContinuationOptions.ExecuteSynchronously .

Por lo tanto, cuando se ejecuta el Yield "más interno", lo que se obtiene son 3000 tareas incompletas, y cada tarea "interna" contiene una devolución de llamada completa que completa la tarea más próxima a la interna. Todo esto está en el montón.

Cuando el Yield más interno se reanuda (en un subproceso de grupo de subprocesos), la continuación (sincrónicamente) ejecuta el resto del método de Test , que completa su tarea, que (sincrónicamente) ejecuta el resto del método de Test , que completa su tarea, etc. , unas miles de veces Por lo tanto, la pila de llamadas en ese subproceso de grupo de subprocesos está creciendo a medida que se completa cada tarea.

Personalmente, me parece sorprendente este comportamiento y lo reporté como un error. Sin embargo, el error fue cerrado por Microsoft como "por diseño". Es interesante observar que la especificación Promises en JavaScript (y, por extensión, el comportamiento de await ) siempre tiene terminaciones prometedoras que se ejecutan de forma asincrónica y nunca sincrónicamente. Esto ha confundido a algunos desarrolladores de JS, pero es el comportamiento que esperaría.

Por lo general, funciona bien y ExecuteSynchronously actúa como una mejora de rendimiento menor. Pero como ha notado, hay escenarios como "recursividad asíncrona" donde puede causar una StackOverflowException .

Hay algunas heurísticas en el BCL para ejecutar continuaciones de forma asincrónica si la pila está demasiado llena , pero no son más que heurísticas y no siempre funcionan.

¿Por qué la StackOverflowException ocurre antes en el caso de Exception que cuando no lanzamos una excepción?

Esa es una gran pregunta. No tengo idea. :)

Tenemos muchos métodos asincrónicos anidados y vemos un comportamiento que realmente no entendemos. Tomemos como ejemplo esta simple aplicación de consola C #

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsyncStackSample { class Program { static void Main(string[] args) { try { var x = Test(index: 0, max: int.Parse(args[0]), throwException: bool.Parse(args[1])).GetAwaiter().GetResult(); Console.WriteLine(x); } catch(Exception ex) { Console.WriteLine(ex); } Console.ReadKey(); } static async Task<string> Test(int index, int max, bool throwException) { await Task.Yield(); if(index < max) { var nextIndex = index + 1; try { Console.WriteLine($"b {nextIndex} of {max} (on threadId: {Thread.CurrentThread.ManagedThreadId})"); return await Test(nextIndex, max, throwException).ConfigureAwait(false); } finally { Console.WriteLine($"e {nextIndex} of {max} (on threadId: {Thread.CurrentThread.ManagedThreadId})"); } } if(throwException) { throw new Exception(""); } return "hello"; } } }

Cuando ejecutamos esta muestra con los siguientes argumentos:

AsyncStackSample.exe 2000 false

Obtenemos una StackOverflowException y este es el último mensaje que vemos en la consola:

e 331 of 2000 (on threadId: 4)

Cuando cambiamos los argumentos en

AsyncStackSample.exe 2000 true

Terminamos con este mensaje

e 831 of 2000 (on threadId: 4)

Entonces StackOverflowException ocurre en el desenrollado de la pila (no estamos seguros si deberíamos llamarlo así, pero la StackOverflowException ocurre después de la llamada recursiva en nuestra muestra, en código síncrono, una StackOverflowException siempre ocurrirá en la llamada al método anidado). En el caso de que arrojemos una excepción, la StackOverflowException ocurre incluso antes.

Sabemos que podemos resolver esto llamando a Task.Yield () en el bloque finally, pero tenemos algunas preguntas:

  1. ¿Por qué la pila crece en el camino de desenrollado (en comparación con un método que no provoca un cambio de hilo en espera)?
  2. ¿Por qué la StackOverflowException ocurre antes en el caso de Exception que cuando no lanzamos una excepción?