简体   繁体   中英

System.Threading.Timer stuck for some time when server has load

We have below class members for timer:

private Timer _activityTimer;

Instantiating this timer variable in one method:

_activityTimer = 
    new Timer(async (timerState) => await UpdateActivityAsync(_ipAddress), null, new Random().Next(1, 15000), 15000); 

But this did not calling periodically when server has load.

It is showing below log in serilog:

  1. Starting HttpMessageHandler cleanup cycle with {InitialCount} items
  2. Ending HttpMessageHandler cleanup cycle after {ElapsedMilliseconds}ms - processed: {DisposedCount} items - remaining: {RemainingItems} items

To handle async periodic callback, I would use System.Threading.PeriodicTimer . This way the execution of the next UpdateActivityAsync will not begin until the last one is done. If you still face thread pool starvation issue, you could manually create an additional thread in which you run the timer.

class Example {
    private PeriodicTimer _activityTimer;
    private IPAddress     _ipAddress = IPAddress.Parse("192.168.1.1");

    public void StartTimer() {
        // Starting the timer, but not awaiting to not block the calling thread
        // If you still face thread pool starvation issue, you could manually create an additional thread here
        StartTimerLoopAsync();
    }

    public void StopTimer() {
        _activityTimer.Dispose();
    }

    private async Task StartTimerLoopAsync() {
        _activityTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(15000));
        while (await _activityTimer.WaitForNextTickAsync()) {
            await UpdateActivityAsync(_ipAddress);
        }
    }

    private async Task UpdateActivityAsync(IPAddress ipAddress) {
        await Task.Delay(500); // Simulate some IO
        Console.WriteLine(ipAddress);
    }
}

UPDATE for .NET Core 3.1

Instead of PeriodicTimer you could simply use Task.Delay (it's not very accurate, but good enough for your use case I believe):

class Example {
    private CancellationTokenSource _cancellationTokenSource = new ();
    private IPAddress               _ipAddress               = IPAddress.Parse("192.168.1.1");

    public void StartTimer() {
        // Starting the timer, but not awaiting to not block the calling thread
        // If you still face thread pool starvation issue, you could manually create an additional thread here
        StartTimerLoopAsync();
    }

    public void StopTimer() {
        _cancellationTokenSource.Cancel();
    }

    private async Task StartTimerLoopAsync() {
        await Task.Delay(new Random().Next(1, 15000)); // System.Threading.Timer first delay
        
        while (!_cancellationTokenSource.IsCancellationRequested) {
            // run the Delay and UpdateActivityAsync simultaneously and wait for both
            var delayTask = Task.Delay(15000, _cancellationTokenSource.Token);
            await UpdateActivityAsync(_ipAddress, _cancellationTokenSource.Token);
            await delayTask;
        }
    }

    private async Task UpdateActivityAsync(IPAddress ipAddress, CancellationToken cancellationToken) {
        await Task.Delay(1500, cancellationToken); // Simulate some IO
        Console.WriteLine(ipAddress);
    }
}

UPDATE

You could also create your own async Timer:

class Example : IDisposable {
    private AsyncTimer<Example>? _timer;
    public  IPAddress            IpAddress = IPAddress.Parse("192.168.1.1");

    public void StartTimer() {
        if (_timer is not null) {
            return;
        }

        _timer = new AsyncTimer<Example>(async (state, ct) => await UpdateActivityAsync(state.IpAddress, ct), this, new Random().Next(1, 15000), 15000);
    }

    public void StopTimer() {
        _timer?.Stop();
    }

    private async Task UpdateActivityAsync(IPAddress ipAddress, CancellationToken cancellationToken) {
        await Task.Delay(500, cancellationToken); // Simulate some IO
        Console.WriteLine(ipAddress);
    }

    public void Dispose() {
        _timer?.Dispose();
    }
}

class AsyncTimer<T> : IDisposable {
    public delegate Task AsyncTimerDelegate(T state, CancellationToken cancellationToken);

    private readonly CancellationTokenSource _cancellationTokenSource = new();
    private readonly AsyncTimerDelegate      _timerCallback;
    private readonly TimeSpan                _dueTime;
    private readonly TimeSpan                _interval;
    private readonly T                       _state;

    public AsyncTimer(AsyncTimerDelegate timerCallback, T state, int dueTime, int interval) {
        _timerCallback = timerCallback;
        _state         = state;
        _dueTime       = TimeSpan.FromMilliseconds(dueTime);
        _interval      = TimeSpan.FromMilliseconds(interval);

        // Starting the timer, but not awaiting to not block the calling thread
        // If you still face thread pool starvation issue, you could manually create an additional thread here
        StartTimerLoopAsync();
    }

    public void Stop() {
        _cancellationTokenSource.Cancel();
    }

    private async Task StartTimerLoopAsync() {
        await Task.Delay(_dueTime);

        while (!_cancellationTokenSource.IsCancellationRequested) {
            // run the Delay and UpdateActivityAsync simultaneously and wait for both
            var delayTask = Task.Delay(_interval, _cancellationTokenSource.Token);
            await _timerCallback.Invoke(_state, _cancellationTokenSource.Token);
            await delayTask;
        }
    }

    public void Dispose() {
        _cancellationTokenSource.Dispose();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM