c# - traves - Rendimiento muy bajo de la tarea asincrónica ejecutada en el grupo de subprocesos en.Net nativo
thread c# windows form (1)
He observado una diferencia extraña en el código nativo administrado frente a .Net. Tengo un trabajo pesado redirigido a threadpool. Cuando ejecuto la aplicación en código administrado, todo funciona sin problemas, pero tan pronto como enciendo la compilación nativa, la tarea se ejecuta pocas veces más lenta y tan lenta que cuelga el subproceso de la interfaz de usuario (supongo que la CPU está tan sobrecargada).
Aquí hay dos capturas de pantalla de salida de depuración, la de la izquierda es del código administrado, y la de la derecha es de compilación nativa. Como puede ver, el tiempo consumido por la tarea de UI es casi el mismo en ambos casos, hasta cuando se inicia el trabajo de subprocesos; luego, en la versión administrada, el tiempo transcurrido de UI aumenta (de hecho, la IU se bloquea y no puede realizar ninguna acción). Los tiempos del trabajo de subprocesos hablan por sí mismos.
El código de muestra para reproducir el problema:
private int max = 2000;
private async void UIJob_Click(object sender, RoutedEventArgs e)
{
IProgress<int> progress = new Progress<int>((p) => { MyProgressBar.Value = (double)p / max; });
await Task.Run(async () => { await SomeUIJob(progress); });
}
private async Task SomeUIJob(IProgress<int> progress)
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < max; i++)
{
if (i % 100 == 0) { Debug.WriteLine($" UI time elapsed => {watch.ElapsedMilliseconds}"); watch.Restart(); }
await Task.Delay(1);
progress.Report(i);
}
}
private async void ThreadpoolJob_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Firing on Threadpool");
await Task.Run(() =>
{
double a = 0.314;
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 50000000; i++)
{
a = Math.Sqrt(a) + Math.Sqrt(a + 1) + i;
if (i % 10000000 == 0) { Debug.WriteLine($"Threadpool -> a value = {a} got in {watch.ElapsedMilliseconds} ms"); watch.Restart(); };
}
});
Debug.WriteLine("Finished with Threadpool");
}
Si necesita una muestra completa, puede descargarla aquí .
Como ya probé, la diferencia aparece tanto en el código optimizado como en el no optimizado, tanto en las versiones de depuración como de lanzamiento.
¿Alguien tiene una idea de lo que puede causar el problema?
Este problema se debe a que el ciclo matemático "ThreadPool" está causando la inanición de la GC. Esencialmente, el GC ha decidido que debe ejecutarse (debido a que quiere hacer una asignación de interoperabilidad) y está tratando de detener todos los hilos para hacer la recolección / compactación. Desafortunadamente, no hemos agregado la capacidad de .NET Native para secuestrar bucles calientes como el que tienes a continuación. Esto se menciona brevemente en la página Migrar su aplicación de Windows Store a .NET Native como:
Hacer bucles infinitos sin hacer una llamada (por ejemplo, mientras (verdadero);) en un hilo puede paralizar la aplicación. Del mismo modo, esperas grandes o infinitas pueden detener la aplicación.
Una forma de evitar esto es agregar un sitio de llamada en su ciclo (¡el GC está feliz de interrumpir su hilo cuando intenta llamar a otro método!).
for (long i = 0; i < 5000000000; i++)
{
MaybeGCMeHere(); // new callsite
a = Math.Sqrt(a) + Math.Sqrt(a + 1) + i;
if (i % 1000000000 == 0) { Debug.WriteLine($"Threadpool -> a value = {a} got in {watch.ElapsedMilliseconds} ms"); watch.Restart(); };
}
...
[MethodImpl(MethodImplOptions.NoInlining)] // need this so the callsite isn’t optimized away
private void MaybeGCMeHere()
{
}
La desventaja es que tendrás este truco de aspecto "feo" y es posible que sufras un poco por las instrucciones adicionales. Les he dicho a algunas personas que esto que supuestamente era "extremadamente raro" es realmente alcanzado por un cliente y veremos qué se puede hacer al respecto.
Gracias por el informe!
Actualización: hemos realizado algunas mejoras importantes en este escenario y podremos secuestrar la mayoría de los hilos de larga ejecución para GC. Estas correcciones estarán disponibles en el conjunto de herramientas de UWP de Actualización 2 probablemente en abril? (No controlo el calendario de envío :-))
Actualizar actualización: las nuevas herramientas están ahora disponibles como parte de las herramientas UWP 1.3.1. No esperamos tener una solución perfecta para los hilos que luchan agresivamente contra el secuestro del GC, pero espero que este escenario sea mucho mejor con las herramientas más recientes. ¡Haznos saber!