tipos tipo puede programacion metodos metodo funciones expresiones expresion estructura ejemplos delegado convertir consola c# async-await task-parallel-library

c# - tipo - ¿Por qué las funciones asincrónicas se llaman dos veces?



no se puede convertir expresion lambda en el tipo delegado (3)

Estoy usando Threading timer para hacer un trabajo periódico:

private static async void TimerCallback(object state) { if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0) { return; } var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i)); await Task.WhenAll(tasksRead); var tasksRecord = tasksRead.Where(x => x.Result != null).Select(x => RecordReadingAsync(x.Result)); await Task.WhenAll(tasksRecord); Interlocked.Decrement(ref currentlyRunningTasksCount); }

Hice la WhenAll temporizador y utilicé WhenAll . En cada función asíncrona que trabaja tengo una salida de consola, que muestra actividad. Ahora el problema es que en el segundo evento de temporizador, cada función asíncrona funciona dos veces por alguna razón. El temporizador está configurado en un período largo. La aplicación es del tipo consola de Windows. ¿Es Select que de alguna manera lo hace funcionar dos veces?


¡Creo que sé POR QUÉ! En dos palabras: razonar que la función con espera impliciti crea un hilo de devolución de llamada. Mejor puedes ver cómo lo explica Jeffrey Richter en este video https://wintellectnow.com/Videos/Watch?videoId=performing-io-bound-asynchronous-operations from 00:17:25

solo inténtalo:

var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i)); var tasksRecord = tasksRead.Where(x => x.Result != null).Select(x => RecordReadingAsync(x.Result)); await Task.WhenAll(tasksRead); await Task.WhenAll(tasksRecord);


Cuando utiliza Task.WhenAll Cuando Task.WhenAll en un IEnumerable<Task<T>> devolverá un T[] de los resultados de Tareas completados. Debe guardar esa variable y usarla o de lo contrario terminará con las enumeraciones múltiples como mencionó Henzi en su respuesta .

Aquí hay una solución sin la innecesaria llamada de .ToList()

private static async void TimerCallback(object state) { if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0) { return; } var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i)); var finshedTasks = await Task.WhenAll(tasksRead); var tasksRecord = finshedTasks.Where(x => x != null).Select(x => RecordReadingAsync(x)); await Task.WhenAll(tasksRecord); Interlocked.Decrement(ref currentlyRunningTasksCount); }


Esta:

var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i));

crea un IEnumerable evaluado perezosamente que asigna números a los resultados de invocación de métodos. ReadSensorsAsync no se invoca aquí, se invocará durante la evaluación.

Este IEnumerable se evalúa dos veces. Aquí:

await Task.WhenAll(tasksRead);

y aquí:

// Here, another lazy IEnumerable is created based on tasksRead. var tasksRecord = tasksRead.Where(...).Select(...); await Task.WhenAll(tasksRecord); // Here, it is evaluated.

Por lo tanto, ReadSensorsAsync se invoca dos veces.

Como csharpfolk sugirió en los comentarios, la materialización de IEnumerable debería solucionar esto:

var tasksRead = Enumerable.Range(3, 35).Select(i => ReadSensorsAsync(i)).ToList();