C#每X分钟运行一个线程,但只有在该线程没有运行的情况下
我有一个C#程序需要每X分钟发送一个线程, 但只有当前调度线程(从X分钟)之前,目前还没有运行 。
一个简单的旧的Timer
本身将无法工作(因为它每隔X分钟发送一个事件,无论先前调度的过程是否完成)。
将要分派的stream程在执行任务所需的时间内差异很大 – 有时可能需要一秒,有时可能需要几个小时。 我不想再次启动该进程,如果它仍然从最后一次启动处理。
任何人都可以提供一些工作的C#示例代码?
在我看来,在这种情况下去的方式是使用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... } }
在这个例子中我使用了System.Timers.Timer
,但是我相信它也可以和其他的定时器一起工作。 BackgroundWorker
类还支持进度报告和取消,并使用事件驱动模型与调度线程进行通信,因此您不必担心易失性variables等问题。
编辑
以下是更详细的示例,包括取消和进度报告:
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 } } }
然后,为了取消进一步的执行,只需调用worker.CancelAsync()
。 请注意,这是完全用户处理的取消机制(它不支持线程中止或任何类似的开箱即用)。
你可以保持一个易变的布尔值来实现你所问的:
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; } }
您可以禁用并启用您的计时器在其已过的callback。
public void TimerElapsed(object sender, EventArgs e) { _timer.Stop(); //Do Work _timer.Start(); }
您可以使用System.Threading.Timer
并在处理数据/方法之前将“ Timeout
设置为“ Infinite
,然后在完成后重新启动Timer
准备进行下一次调用。
private System.Threading.Timer _timerThread; private int _period = 2000; public MainWindow() { InitializeComponent(); _timerThread = new System.Threading.Timer((o) => { // Stop the timer; _timerThread.Change(-1, -1); // Process your data ProcessData(); // start timer again (BeginTime, Interval) _timerThread.Change(_period, _period); }, null, 0, _period); } private void ProcessData() { // do stuff; }
如果你想让定时器的callback在后台线程上触发,你可以使用System.Threading.Timer 。 此Timer类允许您“指定Timeout.Infinite
禁用定期信号”。 作为构造函数的一部分,它使计时器只触发一次。
然后,您可以构build一个新的计时器,当您的第一个计时器的callback触发并完成,防止多个计时器被安排,直到你准备好发生。
这样做的好处是你不会创build计时器,然后反复取消它们,因为你从来没有计划过比你的“下一个事件”更多的时间。
至less有20种不同的方法来完成这个任务,从使用定时器和信号量到volatilevariables,使用TPL,使用像Quartz等开源调度工具。
创build一个线程是一个昂贵的练习,所以为什么不创build一个,让它在后台运行,因为它将花费其大部分时间空闲,这不会造成系统的真正stream失。 定期醒来,开始工作,然后再回到睡觉的时间。 无论任务需要多长时间,您将始终至less等待完成之后的“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); }
你可以使用System.Threading.Timer。 诀窍是只设置初始时间。 当前一个时间间隔结束或工作完成时,再次设置初始时间(当工作需要的时间比间隔长时,会发生这种情况)。 这里是示例代码。
class Program { static System.Threading.Timer timer; static bool workAvailable = false; static int timeInMs = 5000; static object o = new object(); static void Main(string[] args) { timer = new Timer((o) => { try { if (workAvailable) { // do the work, whatever is required. // if another thread is started use Thread.Join to wait for the thread to finish } } catch (Exception) { // handle } finally { // only set the initial time, do not set the recurring time timer.Change(timeInMs, Timeout.Infinite); } }); // only set the initial time, do not set the recurring time timer.Change(timeInMs, Timeout.Infinite); }
为什么不使用Monitor.TryEnter()
定时器? 如果OnTimerElapsed()
在前一个线程完成之前再次被调用,它将被丢弃,并且另一次尝试将不会再次发生,直到定时器再次触发。
private static readonly object _locker = new object(); private void OnTimerElapsed(object sender, ElapsedEventArgs e) { if (!Monitor.TryEnter(_locker)) { return; } // Don't let multiple threads in here at the same time. try { // do stuff } finally { Monitor.Exit(_locker); } }
您可以在任务之前停止计时器,并在任务完成之后再次启动计时器,这样可以使您的计时在间隔时间内周期性执行。
public void myTimer_Elapsed(object sender, EventArgs e) { myTimer.Stop(); // Do something you want here. myTimer.Start(); }
在我的post中使用PeriodicTaskFactory
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); Task task = PeriodicTaskFactory.Start(() => { Console.WriteLine(DateTime.Now); Thread.Sleep(5000); }, intervalInMilliseconds: 1000, synchronous: true, cancelToken: cancellationTokenSource.Token); Console.WriteLine("Press any key to stop iterations..."); Console.ReadKey(true); cancellationTokenSource.Cancel(); Console.WriteLine("Waiting for the task to complete..."); Task.WaitAny(task);
下面的输出显示,即使间隔设置为1000毫秒,每个迭代都不会启动,直到任务操作的工作完成。 这是使用synchronous: true
可选参数完成的。
Press any key to stop iterations... 9/6/2013 1:01:52 PM 9/6/2013 1:01:58 PM 9/6/2013 1:02:04 PM 9/6/2013 1:02:10 PM 9/6/2013 1:02:16 PM Waiting for the task to complete... Press any key to continue . . .
UPDATE
如果你想使用PeriodicTaskFactory的“跳过事件”行为,就不要使用同步选项,像Bob所做的那样实现Monitor.TryEnter https://stackoverflow.com/a/18665948/222434
Task task = PeriodicTaskFactory.Start(() => { if (!Monitor.TryEnter(_locker)) { return; } // Don't let multiple threads in here at the same time. try { Console.WriteLine(DateTime.Now); Thread.Sleep(5000); } finally { Monitor.Exit(_locker); } }, intervalInMilliseconds: 1000, synchronous: false, cancelToken: cancellationTokenSource.Token);
PeriodicTaskFactory
Task.Wait
在于返回的Task可以用于所有的TPL API,例如Task.Wait
,continuations等。
如果我正确理解你,你实际上只是想确保你的线程没有运行,然后再调度另一个线程。 假设你的class上有一个像这样定义的线程。
private System.Threading.Thread myThread;
你可以做:
//inside some executed method System.Threading.Timer t = new System.Threading.Timer(timerCallBackMethod, null, 0, 5000);
然后像这样添加callBack
private void timerCallBackMethod(object state) { if(myThread.ThreadState == System.Threading.ThreadState.Stopped || myThread.ThreadState == System.Threading.ThreadState.Unstarted) { //dispatch new thread } }
这应该做你想要的。 它执行一个线程,然后join线程直到完成。 进入一个计时器循环,以确保它不提前执行线程,然后再次closures并执行。
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!"); } } }
应该产生类似于:
在计时器function! 睡了一下! 好的,进入另一个线程创build! 在计时器function! 睡了一下! 好的,进入另一个线程创build! 在计时器function! ……
希望这可以帮助! SR
这个的问题是ExecuteTaskCallback
方法。 这一点是负责做一些工作,但只有在它尚未这样做。 为此,我使用了一个ManualResetEvent
( canExecute
),它最初设置为在StartTaskCallbacks
方法中发送信号。
请注意我使用canExecute.WaitOne(0)
。 零表示WaitOne
将立即返回WaitHandle
( MSDN )的状态。 如果省略了零,则最终每次调用ExecuteTaskCallback
最终都会运行该任务,这可能相当不幸。
另一个重要的事情是能够干净地结束处理。 我select阻止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(); }
前段时间我也有同样的问题,我所做的只是使用lock {}语句。 有了这个,即使Timer要做任何事情,他也不得不等待,直到lockingBlock结束。
即
lock { // this code will never be interrupted or started again until it has finished }
这是一个很好的方法可以肯定,你的过程将工作,直到最后不中断。
这个问题已经有了很多很好的答案,其中包括基于TPL中一些function的稍微更新的问题。 但我觉得这里缺乏:
- 基于TPL的解决schemea)并不完全包含在这里,而是指另一个答案,b)没有说明如何使用
async
/await
来实现单一方法中的时序机制,以及c)参考实现相当复杂,这有点混淆了这个特定问题的潜在相关点。 - 这里的原始问题在所期望的实现的具体参数上有些模糊(尽pipe其中一些在注释中已经阐明)。 同时,其他读者可能有相似但不相同的需求,没有人回答可能需要的各种devise选项。
- 我特别喜欢使用
Task
和async
/执行定期行为,因为它简化了代码。 特别是async
/await
特性在代码中是非常有价值的,否则这些代码会被连续/callback实现细节所破坏,并以单一方法保留其自然的线性逻辑。 但这里没有答案表明简单。
所以,这个理由促使我对这个问题又增加了一个答案。
对我来说,首先要考虑的是“这里有什么确切的行为?” 这里的问题从一个基本的前提开始:即使任务花费比定时器周期更长的时间,定时器启动的周期任务也不应该同时运行。 但是,可以通过多种方式来实现前提,其中包括:
- 在任务运行时,甚至不要运行计时器。
- 运行定时器(这个和我在这里展示的其余选项都假设定时器在任务执行期间继续运行),但是如果任务花费的时间比定时器周期长,那么在任务完成之后立即再次运行任务先前的计时器打勾
- 只有在计时器滴答时间开始执行任务。 如果任务花费的时间超过了定时器周期,那么在当前执行任务的时候不要开始一个新任务,即使当前任务已经完成,也不要开始新的任务,直到下一个定时器打勾为止。
- 如果任务花费的时间超过了定时器的时间间隔,那么不仅要在任务完成后立即再次运行任务,而且还要根据需要多次运行任务,直到任务“赶上”为止。 即随着时间的推移,尽最大努力为每个计时器滴答执行一次任务。
根据评论,我的印象是#3选项最符合OP的原始要求,尽pipe听起来像#1选项可能也会起作用。 但选项#2和#4可能比其他人更可取。
在下面的代码示例中,我用五种不同的方法实现了这些选项(其中两个实现了#3选项,但方式稍有不同)。 当然,人们会根据自己的需要select合适的实施方式。 你可能不需要一个程序中的所有五个! 🙂
关键在于,在所有这些实现中,它们自然而然地以一种非常简单的方式,以一种非周期性的方式执行任务。 也就是说,他们有效地实现了一个基于计时器的执行模型,同时确保任务一次只能由一个线程执行,而且是根据问题的主要要求。
这个例子还说明了如何使用CancellationTokenSource
来中断周期任务,利用await
来以一种干净而简单的方式处理基于exception的模型。
class Program { const int timerSeconds = 5, actionMinSeconds = 1, actionMaxSeconds = 7; static Random _rnd = new Random(); static void Main(string[] args) { Console.WriteLine("Press any key to interrupt timer and exit..."); Console.WriteLine(); CancellationTokenSource cancelSource = new CancellationTokenSource(); new Thread(() => CancelOnInput(cancelSource)).Start(); Console.WriteLine( "Starting at {0:HH:mm:ss.f}, timer interval is {1} seconds", DateTime.Now, timerSeconds); Console.WriteLine(); Console.WriteLine(); // NOTE: the call to Wait() is for the purpose of this // specific demonstration in a console program. One does // not normally use a blocking wait like this for asynchronous // operations. // Specify the specific implementation to test by providing the method // name as the second argument. RunTimer(cancelSource.Token, M1).Wait(); } static async Task RunTimer( CancellationToken cancelToken, Func<Action, TimeSpan, Task> timerMethod) { Console.WriteLine("Testing method {0}()", timerMethod.Method.Name); Console.WriteLine(); try { await timerMethod(() => { cancelToken.ThrowIfCancellationRequested(); DummyAction(); }, TimeSpan.FromSeconds(timerSeconds)); } catch (OperationCanceledException) { Console.WriteLine(); Console.WriteLine("Operation cancelled"); } } static void CancelOnInput(CancellationTokenSource cancelSource) { Console.ReadKey(); cancelSource.Cancel(); } static void DummyAction() { int duration = _rnd.Next(actionMinSeconds, actionMaxSeconds + 1); Console.WriteLine("dummy action: {0} seconds", duration); Console.Write(" start: {0:HH:mm:ss.f}", DateTime.Now); Thread.Sleep(TimeSpan.FromSeconds(duration)); Console.WriteLine(" - end: {0:HH:mm:ss.f}", DateTime.Now); } static async Task M1(Action taskAction, TimeSpan timer) { // Most basic: always wait specified duration between // each execution of taskAction while (true) { await Task.Delay(timer); await Task.Run(() => taskAction()); } } static async Task M2(Action taskAction, TimeSpan timer) { // Simple: wait for specified interval, minus the duration of // the execution of taskAction. Run taskAction immediately if // the previous execution too longer than timer. TimeSpan remainingDelay = timer; while (true) { if (remainingDelay > TimeSpan.Zero) { await Task.Delay(remainingDelay); } Stopwatch sw = Stopwatch.StartNew(); await Task.Run(() => taskAction()); remainingDelay = timer - sw.Elapsed; } } static async Task M3a(Action taskAction, TimeSpan timer) { // More complicated: only start action on time intervals that // are multiples of the specified timer interval. If execution // of taskAction takes longer than the specified timer interval, // wait until next multiple. // NOTE: this implementation may drift over time relative to the // initial start time, as it considers only the time for the executed // action and there is a small amount of overhead in the loop. See // M3b() for an implementation that always executes on multiples of // the interval relative to the original start time. TimeSpan remainingDelay = timer; while (true) { await Task.Delay(remainingDelay); Stopwatch sw = Stopwatch.StartNew(); await Task.Run(() => taskAction()); long remainder = sw.Elapsed.Ticks % timer.Ticks; remainingDelay = TimeSpan.FromTicks(timer.Ticks - remainder); } } static async Task M3b(Action taskAction, TimeSpan timer) { // More complicated: only start action on time intervals that // are multiples of the specified timer interval. If execution // of taskAction takes longer than the specified timer interval, // wait until next multiple. // NOTE: this implementation computes the intervals based on the // original start time of the loop, and thus will not drift over // time (not counting any drift that exists in the computer's clock // itself). TimeSpan remainingDelay = timer; Stopwatch swTotal = Stopwatch.StartNew(); while (true) { await Task.Delay(remainingDelay); await Task.Run(() => taskAction()); long remainder = swTotal.Elapsed.Ticks % timer.Ticks; remainingDelay = TimeSpan.FromTicks(timer.Ticks - remainder); } } static async Task M4(Action taskAction, TimeSpan timer) { // More complicated: this implementation is very different from // the others, in that while each execution of the task action // is serialized, they are effectively queued. In all of the others, // if the task is executing when a timer tick would have happened, // the execution for that tick is simply ignored. But here, each time // the timer would have ticked, the task action will be executed. // // If the task action takes longer than the timer for an extended // period of time, it will repeatedly execute. If and when it // "catches up" (which it can do only if it then eventually // executes more quickly than the timer period for some number // of iterations), it reverts to the "execute on a fixed // interval" behavior. TimeSpan nextTick = timer; Stopwatch swTotal = Stopwatch.StartNew(); while (true) { TimeSpan remainingDelay = nextTick - swTotal.Elapsed; if (remainingDelay > TimeSpan.Zero) { await Task.Delay(remainingDelay); } await Task.Run(() => taskAction()); nextTick += timer; } } }
最后一个注意事项:我在跟上另一个问题的复本之后,才看到这个问答。 在另一个问题,不同于这里,OP特别指出他们正在使用System.Windows.Forms.Timer
类。 当然,这个类主要是因为它具有在UI线程中引发Tick
事件的好处。
现在,它和这个问题都涉及到一个在后台线程中实际执行的任务,因此该类定时器的UI线程关联行为在这些场景中并不是特别有用。 这里的代码被实现为匹配“启动一个后台任务”范例,但是它可以很容易地被改变,以便taskAction
委托被直接调用,而不是在Task
中运行并等待。 除了我上面提到的结构优势之外,使用async
/ await
的好处在于,它保留了System.Windows.Forms.Timer
类所期望的线程相关行为。
我build议使用Timer而不是线程,因为它是较轻的对象。 为了达到你的目标,你可以做以下事情。
using System.Timers; namespace sample_code_1 { public class ClassName { Timer myTimer; static volatile bool isRunning; public OnboardingTaskService() { myTimer= new Timer(); myTimer.Interval = 60000; myTimer.Elapsed += myTimer_Elapsed; myTimer.Start(); } private void myTimer_Elapsed(object sender, ElapsedEventArgs e) { if (isRunning) return; isRunning = true; try { //Your Code.... } catch (Exception ex) { //Handle Exception } finally { isRunning = false; } } } }
让我知道是否有帮助。
在那里我做了,似乎工作!
using System; using System.Collections; using System.Linq; using System.Text; using System.Threading; using System.ComponentModel; using System.Timers; namespace Exercice_1_1 { class Program { static void Main(string[] args) { Console.SetWindowPosition(0, 0); Service backgroundWork = new Service(1, 1000); do { } while (!(backgroundWork.ExecutionCNT == 100)); } } class Service { private BackgroundWorker worker; private int executionCNT = 0; public int ExecutionCNT { get { return executionCNT; } } //Constructeur (Compteur du nombre d'execution, Delais entre chaque executions) public Service(int executionCNT, int executionDelay) { this.executionCNT = executionCNT; worker = new BackgroundWorker(); System.Timers.Timer timer = new System.Timers.Timer(executionDelay); worker.DoWork += worker_DoWork; timer.Elapsed += timer_Elapsed; timer.Start(); } void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (!worker.IsBusy) { worker.RunWorkerAsync(); } } void worker_DoWork(object sender, DoWorkEventArgs e) { Console.SetCursorPosition(0, 0); Console.WriteLine("({0})task started....\n\n", executionCNT++); Console.WriteLine("Press any key to end it! OR ctrl + c to end exemple"); Console.ReadLine(); Console.Clear(); } } }