Ejecute el método asíncrono regularmente con el intervalo especificado

Resuelto Eadel asked hace 9 años • 3 respuestas

Necesito publicar algunos datos en el servicio desde la aplicación web C#. Los datos en sí se recopilan cuando el usuario utiliza la aplicación (una especie de estadísticas de uso). No quiero enviar datos al servicio durante la solicitud de cada usuario, prefiero recopilar los datos en la aplicación y luego enviar todos los datos en una sola solicitud en un hilo separado, que no atiende las solicitudes de los usuarios (quiero decir el usuario no tiene que esperar a que el servicio procese la solicitud). Para ello necesito una especie de setIntervalanálogo de JS: el lanzamiento de la función cada X segundos para enviar todos los datos recopilados al servicio.

Descubrí que Timerla clase proporciona algo similar ( Elapsedevento). Sin embargo, esto permite ejecutar el método sólo una vez, pero eso no es un gran problema. La principal dificultad es que requiere la firma.

void MethodName(object e, ElapsedEventArgs args)

Si bien me gustaría iniciar el método asíncrono, éste llamará al servicio web (los parámetros de entrada no son importantes):

async Task MethodName(object e, ElapsedEventArgs args)

¿Alguien podría aconsejar cómo resolver la tarea descrita? Se agradece cualquier consejo.

Eadel avatar May 26 '15 21:05 Eadel
Aceptado

El asyncequivalente es un whilebucle con Task.Delay(que internamente utiliza a System.Threading.Timer):

public async Task PeriodicFooAsync(TimeSpan interval, CancellationToken cancellationToken)
{
    while (true)
    {
        await FooAsync();
        await Task.Delay(interval, cancellationToken)
    }
}

Es importante pasar a CancellationTokenpara que pueda detener esa operación cuando lo desee (por ejemplo, cuando cierre su aplicación).

Ahora bien, si bien esto es relevante para .Net en general, en ASP.Net es peligroso realizar cualquier tipo de incendio y olvidarse. Hay varias soluciones para esto (como HangFire), algunas están documentadas en Fire and Forget on ASP.NET de Stephen Cleary, otras en Cómo ejecutar tareas en segundo plano en ASP.NET de Scott Hanselman.

i3arnon avatar May 26 '2015 15:05 i3arnon

A continuación se muestra un método que invoca un método asincrónico de forma periódica:

public static async Task PeriodicAsync(Func<Task> action, TimeSpan interval,
    CancellationToken cancellationToken = default)
{
    while (true)
    {
        Task delayTask = Task.Delay(interval, cancellationToken);
        await action();
        await delayTask;
    }
}

El suministrado actionse invoca cada vez intervaly luego Taskse espera el creado. La duración de la espera no afecta al intervalo, a menos que sea mayor. En tal caso prevalecerá el principio de no superposición de ejecución, por lo que el plazo se ampliará hasta igualar la duración de la espera.

En caso de excepción, la PeriodicAsynctarea se completará con error, por lo que si desea que sea resistente a errores, debe incluir un manejo riguroso de errores dentro del archivo action.

Ejemplo de uso:

Task statisticsUploader = PeriodicAsync(async () =>
{
    try
    {
        await UploadStatisticsAsync();
    }
    catch (Exception ex)
    {
        // Log the exception
    }
}, TimeSpan.FromMinutes(5));

Actualización de .NET 6: ahora es posible implementar una funcionalidad casi idéntica sin incurrir en el costo de una Task.Delayasignación en cada bucle, utilizando la nueva PeriodicTimerclase:

public static async Task PeriodicAsync(Func<Task> action, TimeSpan interval,
    CancellationToken cancellationToken = default)
{
    using PeriodicTimer timer = new(interval);
    while (true)
    {
        await action();
        await timer.WaitForNextTickAsync(cancellationToken);
    }
}

El WaitForNextTickAsyncmétodo devuelve a ValueTask<bool>, que es lo que hace que esta implementación sea más eficiente. Sin embargo, la diferencia en eficiencia es bastante minúscula. Para una acción periódica que se ejecuta cada 5 minutos, asignar algunos objetos livianos en cada iteración debería tener un impacto prácticamente nulo.

El comportamiento de la PeriodicTimerimplementación basada en -no es idéntico al de la Task.Delayimplementación basada en -. En caso de que la duración de una acción sea mayor que interval, ambas implementaciones invocarán la siguiente acción inmediatamente después de completar la acción anterior, pero el programador de la PeriodicTimerimplementación basada en -no avanzará como Task.Delaylo hace la implementación basada en -. Consulte el diagrama de mármol a continuación para ver una demostración visual de la diferencia:

Clock          X---------X---------X---------X---------X---------X---------X--
Task.Delay:    +-----|   +---|     +------------|+---|     +------|  +--|
PeriodicTimer: +-----|   +---|     +------------|+---| +------|  +--|      +--

La programación de la Task.Delayimplementación basada en - se adelantó permanentemente, porque la tercera invocación de la actionduró más que la interval.

Como puede ver, PeriodicTimerno garantiza un intervalo mínimo entre invocaciones del archivo action.

Las PeriodicAsyncimplementaciones anteriores se han mantenido simples intencionalmente con fines educativos. Cosas como la validación de argumentos y ConfigureAwaitse han omitido.

Theodor Zoulias avatar Jul 04 '2020 03:07 Theodor Zoulias