“timer + Task.Run” vs “while loop + Task.Delay” in asp.net core hosted service

前端 未结 1 366
梦谈多话
梦谈多话 2021-01-27 15:54

I have a requirement that background service should run Process method every day at 0:00 a.m.

So, one of my team member wrote the following code:



        
相关标签:
1条回答
  • 2021-01-27 16:50

    The while loop approach is simpler and safer. Using the Timer class has two hidden gotchas:

    1. Subsequent events can potentially invoke the attached event handler in an ovelapping manner.
    2. Exceptions thrown inside the handler are swallowed, and this behavior is subject to change in future releases of the .NET Framework. (from the docs)

    Your current while loop implementation can be improved in various ways though:

    1. Reading the DateTime.Now multiple times during a TimeSpan calculation may produce unexpected results, because the DateTime returned by DateTime.Now can be different each time. It is preferable to store the DateTime.Now in a variable, and use the stored value in the calculations.
    2. Checking the condition cancellationToken.IsCancellationRequested in the while loop could result to inconsistent cancellation behavior, if you also use the same token as an argument of the Task.Delay. Skipping this check completely is simpler and consistent. This way cancelling the token will always produce an OperationCanceledException as a result.
    3. Ideally the duration of the Process should not affect the scheduling of the next operation. One way to do it is to create the Task.Delay task before starting the Process, and await it after the completion of the Process. Or you can just recalculate the next delay based on the current time. This has also the advantage that the scheduling will be adjusted automatically in case of a system-wise time change.

    Here is my suggestion:

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        TimeSpan scheduledTime = TimeSpan.FromHours(0); // midnight
        TimeSpan minimumIntervalBetweenStarts = TimeSpan.FromHours(12);
    
        while (true)
        {
            var scheduledDelay = scheduledTime - DateTime.Now.TimeOfDay;
    
            while (scheduledDelay < TimeSpan.Zero)
                scheduledDelay += TimeSpan.FromDays(1);
    
            await Task.Delay(scheduledDelay, cancellationToken);
    
            var delayBetweenStarts =
                Task.Delay(minimumIntervalBetweenStarts, cancellationToken);
    
            await ProcessAsync();
    
            await delayBetweenStarts;
        }
    }
    

    The reason for the minimumIntervalBetweenStarts is to protect from very dramatic system-wise time changes.

    0 讨论(0)
提交回复
热议问题