C# run a thread every X minutes, but only if that

2019-01-16 05:09发布

I have a C# program that needs to dispatch a thread every X minutes, but only if the previously dispatched thread (from X minutes) ago is not currently still running.

A plain old Timer alone will not work (because it dispatches an event every X minutes regardless or whether or not the previously dispatched process has finished yet).

The process that's going to get dispatched varies wildly in the time it takes to perform it's task - sometimes it might take a second, sometimes it might take several hours. I don't want to start the process again if it's still processing from the last time it was started.

Can anyone provide some working C# sample code?

标签: c# timer
16条回答
趁早两清
2楼-- · 2019-01-16 05:44

The guts of this is the ExecuteTaskCallback method. This bit is charged with doing some work, but only if it is not already doing so. For this I have used a ManualResetEvent (canExecute) that is initially set to be signalled in the StartTaskCallbacks method.

Note the way I use canExecute.WaitOne(0). The zero means that WaitOne will return immediately with the state of the WaitHandle (MSDN). If the zero is omitted, you would end up with every call to ExecuteTaskCallback eventually running the task, which could be fairly disastrous.

The other important thing is to be able to end processing cleanly. I have chosen to prevent the Timer from executing any further methods in StopTaskCallbacks because it seems preferable to do so while other work may be ongoing. This ensures that both no new work will be undertaken, and that the subsequent call to canExecute.WaitOne(); will indeed cover the last task if there is one.

private static void ExecuteTaskCallback(object state)
{
    ManualResetEvent canExecute = (ManualResetEvent)state;

    if (canExecute.WaitOne(0))
    {
        canExecute.Reset();
        Console.WriteLine("Doing some work...");
        //Simulate doing work.
        Thread.Sleep(3000);
        Console.WriteLine("...work completed");
        canExecute.Set();
    }
    else
    {
        Console.WriteLine("Returning as method is already running");
    }
}

private static void StartTaskCallbacks()
{
    ManualResetEvent canExecute = new ManualResetEvent(true),
        stopRunning = new ManualResetEvent(false);
    int interval = 1000;

    //Periodic invocations. Begins immediately.
    Timer timer = new Timer(ExecuteTaskCallback, canExecute, 0, interval);

    //Simulate being stopped.
    Timer stopTimer = new Timer(StopTaskCallbacks, new object[]
    {
        canExecute, stopRunning, timer
    }, 10000, Timeout.Infinite);

    stopRunning.WaitOne();

    //Clean up.
    timer.Dispose();
    stopTimer.Dispose();
}

private static void StopTaskCallbacks(object state)
{
    object[] stateArray = (object[])state;
    ManualResetEvent canExecute = (ManualResetEvent)stateArray[0];
    ManualResetEvent stopRunning = (ManualResetEvent)stateArray[1];
    Timer timer = (Timer)stateArray[2];

    //Stop the periodic invocations.
    timer.Change(Timeout.Infinite, Timeout.Infinite);

    Console.WriteLine("Waiting for existing work to complete");
    canExecute.WaitOne();
    stopRunning.Set();
}
查看更多
虎瘦雄心在
3楼-- · 2019-01-16 05:49

If you want the timer's callback to fire on a background thread, you could use a System.Threading.Timer. This Timer class allows you to "Specify Timeout.Infinite to disable periodic signaling." as part of the constructor, which causes the timer to fire only a single time.

You can then construct a new timer when your first timer's callback fires and completes, preventing multiple timers from being scheduled until you are ready for them to occur.

The advantage here is you don't create timers, then cancel them repeatedly, as you're never scheduling more than your "next event" at a time.

查看更多
混吃等死
4楼-- · 2019-01-16 05:50

I had the same problem some time ago and all I had done was using the lock{} statement. With this, even if the Timer wants to do anything, he is forced to wait, until the end of the lock-Block.

i.e.

lock
{    
     // this code will never be interrupted or started again until it has finished
} 

This is a great way to be sure, your process will work until the end without interrupting.

查看更多
爷、活的狠高调
5楼-- · 2019-01-16 05:53

In my opinion the way to go in this situation is to use System.ComponentModel.BackgroundWorker class and then simply check its IsBusy property each time you want to dispatch (or not) the new thread. The code is pretty simple; here's an example:

class MyClass
{    
    private BackgroundWorker worker;

    public MyClass()
    {
        worker = new BackgroundWorker();
        worker.DoWork += worker_DoWork;
        Timer timer = new Timer(1000);
        timer.Elapsed += timer_Elapsed;
        timer.Start();
    }

    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if(!worker.IsBusy)
            worker.RunWorkerAsync();
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        //whatever You want the background thread to do...
    }
}

In this example I used System.Timers.Timer, but I believe it should also work with other timers. The BackgroundWorker class also supports progress reporting and cancellation, and uses event-driven model of communication with the dispatching thread, so you don't have to worry about volatile variables and the like...

EDIT

Here's more elaborate example including cancelling and progress reporting:

class MyClass
{    
    private BackgroundWorker worker;

    public MyClass()
    {
        worker = new BackgroundWorker()
        {
            WorkerSupportsCancellation = true,
            WorkerReportsProgress = true
        };
        worker.DoWork += worker_DoWork;
        worker.ProgressChanged += worker_ProgressChanged;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;

        Timer timer = new Timer(1000);
        timer.Elapsed += timer_Elapsed;
        timer.Start();
    }

    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if(!worker.IsBusy)
            worker.RunWorkerAsync();
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker w = (BackgroundWorker)sender;

        while(/*condition*/)
        {
            //check if cancellation was requested
            if(w.CancellationPending)
            {
                //take any necessary action upon cancelling (rollback, etc.)

                //notify the RunWorkerCompleted event handler
                //that the operation was cancelled
                e.Cancel = true; 
                return;
            }

            //report progress; this method has an overload which can also take
            //custom object (usually representing state) as an argument
            w.ReportProgress(/*percentage*/);

            //do whatever You want the background thread to do...
        }
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        //display the progress using e.ProgressPercentage and/or e.UserState
    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if(e.Cancelled)
        {
            //do something
        }
        else
        {
            //do something else
        }
    }
}

Then, in order to cancel further execution simply call worker.CancelAsync(). Note that this is completely user-handled cancellation mechanism (it does not support thread aborting or anything like that out-of-the-box).

查看更多
Ridiculous、
6楼-- · 2019-01-16 05:55

You can just maintain a volatile bool to achieve what you asked:

private volatile bool _executing;

private void TimerElapsed(object state)
{
    if (_executing)
        return;

    _executing = true;

    try
    {
        // do the real work here
    }
    catch (Exception e)
    {
        // handle your error
    }
    finally
    {
        _executing = false;
    }
}
查看更多
我命由我不由天
7楼-- · 2019-01-16 05:55

This should do what you want. It executes a thread, then joins the thread until it has finished. Goes into a timer loop to make sure it is not executing a thread prematurely, then goes off again and executes.

using System.Threading;

public class MyThread
{
    public void ThreadFunc()
    {
        // do nothing apart from sleep a bit
        System.Console.WriteLine("In Timer Function!");
        Thread.Sleep(new TimeSpan(0, 0, 5));
    }
};

class Program
{
    static void Main(string[] args)
    {
        bool bExit = false;
        DateTime tmeLastExecuted;

        // while we don't have a condition to exit the thread loop
        while (!bExit)
        {
            // create a new instance of our thread class and ThreadStart paramter
            MyThread myThreadClass = new MyThread();
            Thread newThread = new Thread(new ThreadStart(myThreadClass.ThreadFunc));

            // just as well join the thread until it exits
            tmeLastExecuted = DateTime.Now; // update timing flag
            newThread.Start();
            newThread.Join();

            // when we are in the timing threshold to execute a new thread, we can exit
            // this loop
            System.Console.WriteLine("Sleeping for a bit!");

            // only allowed to execute a thread every 10 seconds minimum
            while (DateTime.Now - tmeLastExecuted < new TimeSpan(0, 0, 10));
            {
                Thread.Sleep(100); // sleep to make sure program has no tight loops
            }

            System.Console.WriteLine("Ok, going in for another thread creation!");
        }
    }
}

Should produce something like:

In Timer Function! Sleeping for a bit! Ok, going in for another thread creation! In Timer Function! Sleeping for a bit! Ok, going in for another thread creation! In Timer Function! ... ...

Hope this helps! SR

查看更多
登录 后发表回答