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 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).
单独一个普通的旧计时器将无法工作(因为无论是否先前已调度的进程已完成,它每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#示例代码?
16 个解决方案
#1
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:
在我看来,在这种情况下的方法是使用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 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...
在这个例子中,我使用了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()。请注意,这是完全由用户处理的取消机制(它不支持线程中止或任何类似的开箱即用)。
#2
You can just maintain a volatile bool to achieve what you asked:
你可以保持挥发性的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; }}
#3
You can disable and enable your timer in its elapsed callback.
您可以在已过去的回调中禁用和启用计时器。
public void TimerElapsed(object sender, EventArgs e){ _timer.Stop(); //Do Work _timer.Start();}
#4
You can just use the System.Threading.Timer
and just set the Timeout
to Infinite
before you process your data/method, then when it completes restart the Timer
ready for the next call.
您可以使用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; }
#5
Using the PeriodicTaskFactory from my post here
在我的帖子中使用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);
The output below shows that even though the interval is set 1000 milliseconds, each iteration doesn't start until the work of the task action is complete. This is accomplished using the synchronous: true
optional parameter.
下面的输出显示即使间隔设置为1000毫秒,每次迭代也不会开始,直到任务操作的工作完成。这是使用synchronous:true可选参数完成的。
Press any key to stop iterations...9/6/2013 1:01:52 PM9/6/2013 1:01:58 PM9/6/2013 1:02:04 PM9/6/2013 1:02:10 PM9/6/2013 1:02:16 PMWaiting for the task to complete...Press any key to continue . . .
UPDATE
If you want the "skipped event" behavior with the PeriodicTaskFactory simply don't use the synchronous option and implement the Monitor.TryEnter like what Bob did here https://*.com/a/18665948/222434
如果您希望使用PeriodicTaskFactory的“跳过事件”行为,则不要使用同步选项并实现Monitor.TryEnter,就像Bob在此处所做的那样https://*.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);
The nice thing about the PeriodicTaskFactory
is that a Task is returned that can be used with all the TPL API, e.g. Task.Wait
, continuations, etc.
关于PeriodicTaskFactory的好处是返回了一个可以与所有TPL API一起使用的Task,例如: Task.Wait,continuations等
#6
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();}
#7
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.
如果您希望在后台线程上触发计时器的回调,则可以使用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.
这里的优点是你不创建计时器,然后反复取消计时器,因为你从来没有安排过多次“下一次事件”。
#8
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.
创建一个线程是一项昂贵的练习,所以为什么不创建一个并让它在后台运行,因为它将花费大部分时间IDLE,它不会导致系统真正耗尽。定期醒来并做好工作,然后回去睡觉一段时间。无论任务需要多长时间,在完成任务之前,您将始终至少等待“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); }
#9
You can use System.Threading.Timer. Trick is to set the initial time only. Initial time is set again when previous interval is finished or when job is finished (this will happen when job is taking longer then the interval). Here is the sample code.
您可以使用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); }
#10
Why not use a timer with Monitor.TryEnter()
? If OnTimerElapsed()
is called again before the previous thread finishes, it will just be discarded and another attempt won't happen again until the timer fires again.
为什么不在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); } }
#11
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.
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.
这是一个很好的方法来确保,您的过程将一直工作到最后而不会中断。
#12
This question already has a number of good answers, including a slightly newer one that is based on some of the features in the TPL. But I feel a lack here:
这个问题已经有很多好的答案,包括一个基于TPL中某些功能的稍微更新的答案。但我觉得这里缺乏:
- The TPL-based solution a) isn't really contained wholly here, but rather refers to another answer, b) doesn't show how one could use
async
/await
to implement the timing mechanism in a single method, and c) the referenced implementation is fairly complicated, which somewhat obfuscates the underlying relevant point to this particular question. - The original question here is somewhat vague on the specific parameters of the desired implementation (though some of that is clarified in comments). At the same time, other readers may have similar but not identical needs, and no one answer addresses the variety of design options that might be desired.
- I particularly like implementing periodic behavior using
Task
andasync
/await
this way, because of the way it simplifies the code. Theasync
/await
feature in particular is so valuable in taking code that would otherwise be fractured by a continuation/callback implementation detail, and preserving its natural, linear logic in a single method. But no answer here demonstrates that simplicity.
基于TPL的解决方案a)实际上并不完全包含在这里,而是指另一个答案,b)没有显示如何使用async / await在单个方法中实现计时机制,以及c)引用实现相当复杂,这有点模糊了这个特定问题的基本相关点。
这里的原始问题对于所需实现的具体参数有些模糊(尽管其中一些在评论中已经阐明)。同时,其他读者可能有相似但不完全相同的需求,并且没有一个答案可以解决可能需要的各种设计选项。
我特别喜欢使用Task和async / await来实现周期性行为,因为它简化了代码。特别是async / await功能在获取代码方面非常有价值,否则这些代码会被延续/回调实现细节破坏,并在单个方法中保留其自然的线性逻辑。但这里没有答案表明简单。
So, with that rationale motivating me to add yet another answer to this question…
所以,基于这个理由激励我为这个问题添加另一个答案......
To me, the first thing to consider is "what exact behavior is desired here?" The question here starts with a basic premise: that the period task initiated by the timer should not run concurrently, even if the task takes longer than the timer period. But there are multiple ways that premise can be fulfilled, including:
对我来说,首先要考虑的是“这里需要什么样的行为?”这里的问题从一个基本前提开始:即使任务花费的时间超过计时器周期,计时器启动的周期任务也不应同时运行。但是有多种方式可以实现前提,包括:
- Don't even run the timer while the task is running.
- Run the timer (this and the remaining options I'm presenting here all assume the timer continues to run during the execution of the task), but if the task takes longer than the timer period, run the task again immediately after it's completed from the previous timer tick.
- Only ever initiate execution of the task on a timer tick. If the task takes longer than the timer period, don't start a new task while the current one is executed, and even once the current one has completed, don't start a new one until the next timer tick.
- If the task takes longer than the timer interval, not only run the task again immediately after it's completed, but run it as many times as necessary until the task has "caught up". I.e. over time, make a best effort to execute the task once for every timer tick.
在任务运行时甚至不运行计时器。
运行计时器(这和我在这里提出的其余选项都假定计时器在执行任务期间继续运行),但是如果任务花费的时间超过计时器周期,则在完成任务后立即再次运行任务上一个计时器滴答
只在计时器滴答声中启动任务的执行。如果任务花费的时间超过计时器周期,则在执行当前任务时不要启动新任务,即使当前任务完成,也不要在下一个计时器滴答之前启动新任务。
如果任务花费的时间超过计时器间隔,则不仅在完成任务后立即再次运行任务,而是根据需要多次运行任务,直到任务“赶上”。即随着时间的推移,尽最大努力为每个计时器滴答执行一次任务。
Based on the comments, I have the impression that the #3 option most closely matches the OP's original request, though it sounds like the #1 option possibly would work too. But options #2 and #4 might be preferable to someone else.
基于这些评论,我的印象是#3选项与OP的原始请求最匹配,但听起来#1选项可能也会起作用。但是选项#2和#4可能比其他人更可取。
In the following code example, I have implemented these options with five different methods (two of them implement option #3, but in slightly different ways). Of course, one would select the appropriate implementation for one's needs. You likely don't need all five in one program! :)
在下面的代码示例中,我使用五种不同的方法实现了这些选项(其中两种方法实现了选项#3,但方式略有不同)。当然,可以根据需要选择合适的实施方案。你可能不需要所有五个一个程序! :)
The key point is that in all of these implementations, they naturally and in a very simple way, execute the task in a period-but-non-concurrent way. That is, they effectively implement a timer-based execution model, while ensuring that the task is only ever being executed by one thread at a time, per the primary request of the question.
关键点在于,在所有这些实现中,它们自然而且以非常简单的方式以周期但非并发的方式执行任务。也就是说,它们有效地实现了基于计时器的执行模型,同时确保该任务仅按照问题的主要请求一次由一个线程执行。
This example also illustrates how CancellationTokenSource
can be used to interrupt the period task, taking advantage of await
to handle the exception-based model in a clean, simple way.
此示例还说明了如何使用CancellationTokenSource来中断期间任务,利用等待以干净,简单的方式处理基于异常的模型。
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; } }}
One final note: I came across this Q&A after following it as a duplicate of another question. In that other question, unlike here, the OP had specifically noted they were using the System.Windows.Forms.Timer
class. Of course, this class is used mainly because it has the nice feature that the Tick
event is raised in the UI thread.
最后一点说明:在我将其作为另一个问题的副本后,我遇到了这个问答。在另一个问题中,与此不同,OP特别指出他们使用的是System.Windows.Forms.Timer类。当然,这个类的使用主要是因为它具有在UI线程中引发Tick事件的好功能。
Now, both it and this question involve a task that is actually executed in a background thread, so the UI-thread-affinitied behavior of that timer class isn't really of particular use in those scenarios. The code here is implemented to match that "start a background task" paradigm, but it can easily be changed so that the taskAction
delegate is simply invoked directly, rather than being run in a Task
and awaited. The nice thing about using async
/await
, in addition to the structural advantage I noted above, is that it preserves the thread-affinitied behavior that is desirable from the System.Windows.Forms.Timer
class.
现在,它和这个问题都涉及一个实际在后台线程中执行的任务,因此该定时器类的UI线程关联行为在这些场景中并不是特别有用。这里的代码是为了匹配“启动后台任务”范例而实现的,但它可以很容易地进行更改,以便直接调用taskAction委托,而不是在Task中运行并等待。除了上面提到的结构优势之外,使用async / await的好处是它保留了System.Windows.Forms.Timer类所需的线程关联行为。
#13
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 methodSystem.Threading.Timer t = new System.Threading.Timer(timerCallBackMethod, null, 0, 5000);
then add the callBack like so
然后像这样添加callBack
private void timerCallBackMethod(object state){ if(myThread.ThreadState == System.Threading.ThreadState.Stopped || myThread.ThreadState == System.Threading.ThreadState.Unstarted) { //dispatch new thread }}
#14
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
#15
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.
其中的内容是ExecuteTaskCallback方法。这个位负责做一些工作,但前提是它还没有这样做。为此,我使用了一个最初设置为在StartTaskCallbacks方法中发出信号的ManualResetEvent(canExecute)。
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.
请注意我使用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 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.
另一个重要的事情是能够彻底结束处理。我已经选择阻止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();}
#16
I recommend to use Timer instead of thread, as it's lighter object. To achieve your goal you can do following.
我建议使用Timer而不是thread,因为它是较轻的对象。为了实现您的目标,您可以做到以下。
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; } } }}
Let me know if it helps.
如果有帮助,请告诉我。
#1
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:
在我看来,在这种情况下的方法是使用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 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...
在这个例子中,我使用了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()。请注意,这是完全由用户处理的取消机制(它不支持线程中止或任何类似的开箱即用)。
#2
You can just maintain a volatile bool to achieve what you asked:
你可以保持挥发性的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; }}
#3
You can disable and enable your timer in its elapsed callback.
您可以在已过去的回调中禁用和启用计时器。
public void TimerElapsed(object sender, EventArgs e){ _timer.Stop(); //Do Work _timer.Start();}
#4
You can just use the System.Threading.Timer
and just set the Timeout
to Infinite
before you process your data/method, then when it completes restart the Timer
ready for the next call.
您可以使用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; }
#5
Using the PeriodicTaskFactory from my post here
在我的帖子中使用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);
The output below shows that even though the interval is set 1000 milliseconds, each iteration doesn't start until the work of the task action is complete. This is accomplished using the synchronous: true
optional parameter.
下面的输出显示即使间隔设置为1000毫秒,每次迭代也不会开始,直到任务操作的工作完成。这是使用synchronous:true可选参数完成的。
Press any key to stop iterations...9/6/2013 1:01:52 PM9/6/2013 1:01:58 PM9/6/2013 1:02:04 PM9/6/2013 1:02:10 PM9/6/2013 1:02:16 PMWaiting for the task to complete...Press any key to continue . . .
UPDATE
If you want the "skipped event" behavior with the PeriodicTaskFactory simply don't use the synchronous option and implement the Monitor.TryEnter like what Bob did here https://*.com/a/18665948/222434
如果您希望使用PeriodicTaskFactory的“跳过事件”行为,则不要使用同步选项并实现Monitor.TryEnter,就像Bob在此处所做的那样https://*.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);
The nice thing about the PeriodicTaskFactory
is that a Task is returned that can be used with all the TPL API, e.g. Task.Wait
, continuations, etc.
关于PeriodicTaskFactory的好处是返回了一个可以与所有TPL API一起使用的Task,例如: Task.Wait,continuations等
#6
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();}
#7
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.
如果您希望在后台线程上触发计时器的回调,则可以使用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.
这里的优点是你不创建计时器,然后反复取消计时器,因为你从来没有安排过多次“下一次事件”。
#8
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.
创建一个线程是一项昂贵的练习,所以为什么不创建一个并让它在后台运行,因为它将花费大部分时间IDLE,它不会导致系统真正耗尽。定期醒来并做好工作,然后回去睡觉一段时间。无论任务需要多长时间,在完成任务之前,您将始终至少等待“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); }
#9
You can use System.Threading.Timer. Trick is to set the initial time only. Initial time is set again when previous interval is finished or when job is finished (this will happen when job is taking longer then the interval). Here is the sample code.
您可以使用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); }
#10
Why not use a timer with Monitor.TryEnter()
? If OnTimerElapsed()
is called again before the previous thread finishes, it will just be discarded and another attempt won't happen again until the timer fires again.
为什么不在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); } }
#11
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.
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.
这是一个很好的方法来确保,您的过程将一直工作到最后而不会中断。
#12
This question already has a number of good answers, including a slightly newer one that is based on some of the features in the TPL. But I feel a lack here:
这个问题已经有很多好的答案,包括一个基于TPL中某些功能的稍微更新的答案。但我觉得这里缺乏:
- The TPL-based solution a) isn't really contained wholly here, but rather refers to another answer, b) doesn't show how one could use
async
/await
to implement the timing mechanism in a single method, and c) the referenced implementation is fairly complicated, which somewhat obfuscates the underlying relevant point to this particular question. - The original question here is somewhat vague on the specific parameters of the desired implementation (though some of that is clarified in comments). At the same time, other readers may have similar but not identical needs, and no one answer addresses the variety of design options that might be desired.
- I particularly like implementing periodic behavior using
Task
andasync
/await
this way, because of the way it simplifies the code. Theasync
/await
feature in particular is so valuable in taking code that would otherwise be fractured by a continuation/callback implementation detail, and preserving its natural, linear logic in a single method. But no answer here demonstrates that simplicity.
基于TPL的解决方案a)实际上并不完全包含在这里,而是指另一个答案,b)没有显示如何使用async / await在单个方法中实现计时机制,以及c)引用实现相当复杂,这有点模糊了这个特定问题的基本相关点。
这里的原始问题对于所需实现的具体参数有些模糊(尽管其中一些在评论中已经阐明)。同时,其他读者可能有相似但不完全相同的需求,并且没有一个答案可以解决可能需要的各种设计选项。
我特别喜欢使用Task和async / await来实现周期性行为,因为它简化了代码。特别是async / await功能在获取代码方面非常有价值,否则这些代码会被延续/回调实现细节破坏,并在单个方法中保留其自然的线性逻辑。但这里没有答案表明简单。
So, with that rationale motivating me to add yet another answer to this question…
所以,基于这个理由激励我为这个问题添加另一个答案......
To me, the first thing to consider is "what exact behavior is desired here?" The question here starts with a basic premise: that the period task initiated by the timer should not run concurrently, even if the task takes longer than the timer period. But there are multiple ways that premise can be fulfilled, including:
对我来说,首先要考虑的是“这里需要什么样的行为?”这里的问题从一个基本前提开始:即使任务花费的时间超过计时器周期,计时器启动的周期任务也不应同时运行。但是有多种方式可以实现前提,包括:
- Don't even run the timer while the task is running.
- Run the timer (this and the remaining options I'm presenting here all assume the timer continues to run during the execution of the task), but if the task takes longer than the timer period, run the task again immediately after it's completed from the previous timer tick.
- Only ever initiate execution of the task on a timer tick. If the task takes longer than the timer period, don't start a new task while the current one is executed, and even once the current one has completed, don't start a new one until the next timer tick.
- If the task takes longer than the timer interval, not only run the task again immediately after it's completed, but run it as many times as necessary until the task has "caught up". I.e. over time, make a best effort to execute the task once for every timer tick.
在任务运行时甚至不运行计时器。
运行计时器(这和我在这里提出的其余选项都假定计时器在执行任务期间继续运行),但是如果任务花费的时间超过计时器周期,则在完成任务后立即再次运行任务上一个计时器滴答
只在计时器滴答声中启动任务的执行。如果任务花费的时间超过计时器周期,则在执行当前任务时不要启动新任务,即使当前任务完成,也不要在下一个计时器滴答之前启动新任务。
如果任务花费的时间超过计时器间隔,则不仅在完成任务后立即再次运行任务,而是根据需要多次运行任务,直到任务“赶上”。即随着时间的推移,尽最大努力为每个计时器滴答执行一次任务。
Based on the comments, I have the impression that the #3 option most closely matches the OP's original request, though it sounds like the #1 option possibly would work too. But options #2 and #4 might be preferable to someone else.
基于这些评论,我的印象是#3选项与OP的原始请求最匹配,但听起来#1选项可能也会起作用。但是选项#2和#4可能比其他人更可取。
In the following code example, I have implemented these options with five different methods (two of them implement option #3, but in slightly different ways). Of course, one would select the appropriate implementation for one's needs. You likely don't need all five in one program! :)
在下面的代码示例中,我使用五种不同的方法实现了这些选项(其中两种方法实现了选项#3,但方式略有不同)。当然,可以根据需要选择合适的实施方案。你可能不需要所有五个一个程序! :)
The key point is that in all of these implementations, they naturally and in a very simple way, execute the task in a period-but-non-concurrent way. That is, they effectively implement a timer-based execution model, while ensuring that the task is only ever being executed by one thread at a time, per the primary request of the question.
关键点在于,在所有这些实现中,它们自然而且以非常简单的方式以周期但非并发的方式执行任务。也就是说,它们有效地实现了基于计时器的执行模型,同时确保该任务仅按照问题的主要请求一次由一个线程执行。
This example also illustrates how CancellationTokenSource
can be used to interrupt the period task, taking advantage of await
to handle the exception-based model in a clean, simple way.
此示例还说明了如何使用CancellationTokenSource来中断期间任务,利用等待以干净,简单的方式处理基于异常的模型。
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; } }}
One final note: I came across this Q&A after following it as a duplicate of another question. In that other question, unlike here, the OP had specifically noted they were using the System.Windows.Forms.Timer
class. Of course, this class is used mainly because it has the nice feature that the Tick
event is raised in the UI thread.
最后一点说明:在我将其作为另一个问题的副本后,我遇到了这个问答。在另一个问题中,与此不同,OP特别指出他们使用的是System.Windows.Forms.Timer类。当然,这个类的使用主要是因为它具有在UI线程中引发Tick事件的好功能。
Now, both it and this question involve a task that is actually executed in a background thread, so the UI-thread-affinitied behavior of that timer class isn't really of particular use in those scenarios. The code here is implemented to match that "start a background task" paradigm, but it can easily be changed so that the taskAction
delegate is simply invoked directly, rather than being run in a Task
and awaited. The nice thing about using async
/await
, in addition to the structural advantage I noted above, is that it preserves the thread-affinitied behavior that is desirable from the System.Windows.Forms.Timer
class.
现在,它和这个问题都涉及一个实际在后台线程中执行的任务,因此该定时器类的UI线程关联行为在这些场景中并不是特别有用。这里的代码是为了匹配“启动后台任务”范例而实现的,但它可以很容易地进行更改,以便直接调用taskAction委托,而不是在Task中运行并等待。除了上面提到的结构优势之外,使用async / await的好处是它保留了System.Windows.Forms.Timer类所需的线程关联行为。
#13
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 methodSystem.Threading.Timer t = new System.Threading.Timer(timerCallBackMethod, null, 0, 5000);
then add the callBack like so
然后像这样添加callBack
private void timerCallBackMethod(object state){ if(myThread.ThreadState == System.Threading.ThreadState.Stopped || myThread.ThreadState == System.Threading.ThreadState.Unstarted) { //dispatch new thread }}
#14
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
#15
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.
其中的内容是ExecuteTaskCallback方法。这个位负责做一些工作,但前提是它还没有这样做。为此,我使用了一个最初设置为在StartTaskCallbacks方法中发出信号的ManualResetEvent(canExecute)。
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.
请注意我使用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 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.
另一个重要的事情是能够彻底结束处理。我已经选择阻止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();}
#16
I recommend to use Timer instead of thread, as it's lighter object. To achieve your goal you can do following.
我建议使用Timer而不是thread,因为它是较轻的对象。为了实现您的目标,您可以做到以下。
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; } } }}
Let me know if it helps.
如果有帮助,请告诉我。