ChatGPT解决这个技术问题 Extra ChatGPT

System.Threading.Timer in C# it seems to be not working. It runs very fast every 3 second

I've a timer object. I want it to be run every minute. Specifically, it should run a OnCallBack method and gets inactive while a OnCallBack method is running. Once a OnCallBack method finishes, it (a OnCallBack) restarts a timer.

Here is what I have right now:

private static Timer timer;

private static void Main()
{
    timer = new Timer(_ => OnCallBack(), null, 0, 1000 * 10); //every 10 seconds
    Console.ReadLine();
}

private static void OnCallBack()
{
    timer.Change(Timeout.Infinite, Timeout.Infinite); //stops the timer
    Thread.Sleep(3000); //doing some long operation
    timer.Change(0, 1000 * 10);  //restarts the timer
}

However, it seems to be not working. It runs very fast every 3 second. Even when if raise a period (1000*10). It seems like it turns a blind eye to 1000 * 10

What did I do wrong?

From Timer.Change: "If dueTime is zero (0), the callback method is invoked immediately.". Looks like it's zero to me.
Yes, but so what? there is a period also.
So what if there's a period also? The quoted sentence makes no claims about the period value. It just says "if this value is zero, I'm going to invoke the callback immediately".
Interestingly if you set both dueTime and period to 0, the timer will run every second and start immediately.

R
Robert Harvey

This is not the correct usage of the System.Threading.Timer. When you instantiate the Timer, you should almost always do the following:

_timer = new Timer( Callback, null, TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

This will instruct the timer to tick only once when the interval has elapsed. Then in your Callback function you Change the timer once the work has completed, not before. Example:

private void Callback( Object state )
{
    // Long running operation
   _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );
}

Thus there is no need for locking mechanisms because there is no concurrency. The timer will fire the next callback after the next interval has elapsed + the time of the long running operation.

If you need to run your timer at exactly N milliseconds, then I suggest you measure the time of the long running operation using Stopwatch and then call the Change method appropriately:

private void Callback( Object state )
{
   Stopwatch watch = new Stopwatch();

   watch.Start();
   // Long running operation

   _timer.Change( Math.Max( 0, TIME_INTERVAL_IN_MILLISECONDS - watch.ElapsedMilliseconds ), Timeout.Infinite );
}

I strongly encourage anyone doing .NET and is using the CLR who hasn't read Jeffrey Richter's book - CLR via C#, to read is as soon as possible. Timers and thread pools are explained in great details there.


I don't agree with that private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }. Callback might be called again before an operation is completed.
What I meant was that Long running operation might took much more time then TIME_INTERVAL_IN_MILLISECONDS. What would happen then?
Callback will not be called again, this is the point. This is why we pass Timeout.Infinite as a second parameter. This basically means don't tick again for the timer. Then reschedule to tick after we have completed the operation.
Newbie to threading here - do you think this is possible to do with a ThreadPool, if you pass in the timer? I'm thinking of a scenario where a new thread is spawned to do a job at a certain interval - and then relegated to the thread pool when complete.
The System.Threading.Timer is a thread pool timer, that is executing it's callbacks on the thread pool, not a dedicated thread. After the timer completes the callback routine, the thread that executed the callback goes back in the pool.
C
Community

It is not necessary to stop timer, see nice solution from this post:

"You could let the timer continue firing the callback method but wrap your non-reentrant code in a Monitor.TryEnter/Exit. No need to stop/restart the timer in that case; overlapping calls will not acquire the lock and return immediately."

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }

it's not for my case. I need to stop timer exactly.
Are you trying to prevent entering callback more then one time? If no what are you trying to achieve?
1. Prevent entering to callback more than one time. 2. Prevent executing too much times.
This exactly what it does. #2 is not much overhead as long as it returns right after if statement if object is locked, especially if you have such big interval.
This does not guarantee that the code is called no less than after the last execution (a new tick of the timer could be fired a microsecond after a previous tick released the lock). It depends if this is a strict requirement or not (not entirely clear from the problem's description).
Y
Yousha Aleayoub

Is using System.Threading.Timer mandatory?

If not, System.Timers.Timer has handy Start() and Stop() methods (and an AutoReset property you can set to false, so that the Stop() is not needed and you simply call Start() after executing).


Yes, but it could be a real requirement, or it just happened that timer was chosen because it's the one most used. Sadly .NET has tons of timer objects, overlapping for 90% but still being (sometimes subtly) different. Of course, if it is a requirement, this solution does not apply at all.
As per the documentation: The Systems.Timer class is available in the .NET Framework only. It is not included in the .NET Standard Library and is not available on other platforms, such as .NET Core or the Universal Windows Platform. On these platforms, as well as for portability across all .NET platforms, you should use the System.Threading.Timer class instead.
D
Damien_The_Unbeliever

I would just do:

private static Timer timer;
 private static void Main()
 {
   timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
   Console.ReadLine();
 }

  private static void OnCallBack()
  {
    timer.Dispose();
    Thread.Sleep(3000); //doing some long operation
    timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
  }

And ignore the period parameter, since you're attempting to control the periodicy yourself.

Your original code is running as fast as possible, since you keep specifying 0 for the dueTime parameter. From Timer.Change:

If dueTime is zero (0), the callback method is invoked immediately.


Is it necessary to dispose timer? Why don't you use Change() method?
Disposing the timer every time is absolutely unnecessary and wrong.
@IvanZlatanov Can you please give additional insight on the same. When we call dispose, objects will be cleared from memory and this is good right? All I see is some additional process that might impact the solution. Just thinking loud...
@SudhakarChavali You should always dispose your objects when you're done using them. In this example, because the timer has Change method which can enable / disable the instance, disposing it and creating new one is unnecessary.
U
Umka
 var span = TimeSpan.FromMinutes(2);
 var t = Task.Factory.StartNew(async delegate / () =>
   {
        this.SomeAsync();
        await Task.Delay(span, source.Token);
  }, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

source.Cancel(true/or not);

// or use ThreadPool(whit defaul options thread) like this
Task.Start(()=>{...}), source.Token)

if u like use some loop thread inside ...

public async void RunForestRun(CancellationToken token)
{
  var t = await Task.Factory.StartNew(async delegate
   {
       while (true)
       {
           await Task.Delay(TimeSpan.FromSeconds(1), token)
                 .ContinueWith(task => { Console.WriteLine("End delay"); });
           this.PrintConsole(1);
        }
    }, token) // drop thread options to default values;
}

// And somewhere there
source.Cancel();
//or
token.ThrowIfCancellationRequested(); // try/ catch block requred.