C# 每 X 分钟运行一个线程,但前提是该线程尚未运行

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12570324/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 23:44:11  来源:igfitidea点击:

C# run a thread every X minutes, but only if that thread is not running already

c#timer

提问by Keith Palmer Jr.

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.

我有一个 C# 程序需要每 X 分钟调度一个线程,但前提是先前调度的线程(从 X 分钟开始)当前未仍在运行

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

一个简单的老方法是Timer行不通的(因为它每 X 分钟调度一个事件,无论之前调度的进程是否已经完成)。

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# 示例代码?

回答by scottm

You can disable and enable your timer in its elapsed callback.

您可以在其经过的回调中禁用和启用您的计时器。

public void TimerElapsed(object sender, EventArgs e)
{
  _timer.Stop();

  //Do Work

  _timer.Start();
}

回答by Adil

You can stop timer before the task and start it again after task completion this can make your take perform periodiacally on even interval of time.

您可以在任务之前停止计时器并在任务完成后重新启动它,这可以使您的拍摄在均匀的时间间隔内定期执行。

public void myTimer_Elapsed(object sender, EventArgs e)
{
    myTimer.Stop();
    // Do something you want here.
    myTimer.Start();
}

回答by Reed Copsey

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.Infiniteto disable periodic signaling." as part of the constructor, which causes the timer to fire only a single time.

如果您希望计时器的回调在后台线程上触发,您可以使用System.Threading.Timer。这个 Timer 类允许您“指定Timeout.Infinite禁用周期性信号”。作为构造函数的一部分,这会导致计时器仅触发一次。

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.

这里的优点是您不会创建计时器,然后反复取消它们,因为您一次安排的时间永远不会超过“下一个事件”。

回答by SolidRegardless

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

希望这可以帮助!SR

回答by Matt Johnson-Pint

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

您只需维护一个 volatile bool 即可实现您的要求:

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;
    }
}

回答by Chibueze Opata

If I understand you correctly, you actually just want to ensure your thread is not running before you dispatch another thread. Let's say you have a thread defined in your class like so.

如果我理解正确,您实际上只是想确保您的线程在调度另一个线程之前没有运行。假设您在类中定义了一个线程,如下所示

private System.Threading.Thread myThread;

You can do:

你可以做:

//inside some executed method
System.Threading.Timer t = new System.Threading.Timer(timerCallBackMethod, null, 0, 5000);

then add the callBack like so

然后像这样添加回调

private void timerCallBackMethod(object state)
{
     if(myThread.ThreadState == System.Threading.ThreadState.Stopped || myThread.ThreadState == System.Threading.ThreadState.Unstarted)
     {
        //dispatch new thread
     }
}

回答by nick_w

The guts of this is the ExecuteTaskCallbackmethod. 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 StartTaskCallbacksmethod.

这就是ExecuteTaskCallback方法。这个位负责做一些工作,但前提是它还没有这样做。为此,我使用了最初设置为在方法中发出信号的ManualResetEvent( canExecute) StartTaskCallbacks

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

请注意我使用canExecute.WaitOne(0). 零意味着WaitOne将立即返回WaitHandle( MSDN)的状态。如果省略零,您将在每次调用时ExecuteTaskCallback最终运行任务,这可能是相当灾难性的。

The other important thing is to be able to end processing cleanly. I have chosen to prevent the Timerfrom executing any further methods in StopTaskCallbacksbecause 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.

另一个重要的事情是能够干净地结束处理。我选择阻止Timer执行任何进一步的方法,StopTaskCallbacks因为在其他工作可能正在进行时这样做似乎更可取。这确保了不会进行新的工作,并且如果有的话,随后的调用canExecute.WaitOne();确实会覆盖最后一个任务。

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();
}

回答by Keith

There are at least 20 different ways to accomplish this, from using a timer and a semaphore, to volatile variables, to using the TPL, to using an opensource scheduling tool like Quartz etc al.

至少有 20 种不同的方法来实现这一点,从使用定时器和信号量,到 volatile 变量,到使用 TPL,再到使用像 Quartz 等开源调度工具。

Creating a thread is an expensive exercise, so why not just create ONE and leave it running in the background, since it will spend the majority of its time IDLE, it causes no real drain on the system. Wake up periodically and do work, then go back to sleep for the time period. No matter how long the task takes, you will always wait at least the "waitForWork" timespan after completing before starting a new one.

创建线程是一项代价高昂的工作,所以为什么不创建 ONE 并让它在后台运行,因为它将花费大部分时间空闲,它不会导致系统真正消耗。定期醒来并工作,然后在一段时间内重新入睡。无论任务需要多长时间,在开始新任务之前,您将始终至少等待“waitForWork”时间跨度。

    //wait 5 seconds for testing purposes
    static TimeSpan waitForWork = new TimeSpan(0, 0, 0, 5, 0);
    static ManualResetEventSlim shutdownEvent = new ManualResetEventSlim(false);
    static void Main(string[] args)
    {
        System.Threading.Thread thread = new Thread(DoWork);
        thread.Name = "My Worker Thread, Dude";
        thread.Start();

        Console.ReadLine();
        shutdownEvent.Set();
        thread.Join();
    }

    public static void DoWork()
    {
        do
        {
            //wait for work timeout or shudown event notification
            shutdownEvent.Wait(waitForWork);

            //if shutting down, exit the thread
            if(shutdownEvent.IsSet)
                return;

            //TODO: Do Work here


        } while (true);

    }

回答by Grx70

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

在我看来,在这种情况下的方法是使用System.ComponentModel.BackgroundWorker类,然后在IsBusy每次要分派(或不分派)新线程时简单地检查其属性。代码非常简单;这是一个例子:

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 BackgroundWorkerclass 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...

在这个例子中,我使用了System.Timers.Timer,但我相信它也应该适用于其他计时器。该BackgroundWorker班还支持进度报告和取消,并采用事件驱动的通信模型与调度线程,因此你不必对volatile变量之类的担心...

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).

然后,为了取消进一步的执行,只需调用worker.CancelAsync(). 请注意,这完全是用户处理的取消机制(它不支持线程中止或任何类似的开箱即用)。

回答by souichiro

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.

前段时间我遇到了同样的问题,我所做的只是使用lock{}语句。这样一来,即使 Timer 想做任何事情,他也被迫等待,直到 lock-Block 结束。

i.e.

IE

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.

这是确定的好方法,您的过程将一直运行到最后而不会中断。