我有一个C#程序需要每隔X分钟调度一个线程,但是只有先前调度的线程(从X分钟开始)以前还没有运行。
单独一个普通的Timer
将不起作用(因为无论是否每个X分钟发送一个事件,或者先前发送的进程是否已经完成)。
将要执行调度的进程在执行任务所需的时间内变化很大 - 有时可能需要一秒钟,有时可能需要几个小时。如果它从上次启动时仍在处理,我不想再次启动该过程。
任何人都可以提供一些有效的C#示例代码吗?
答案 0 :(得分:54)
在我看来,在这种情况下的方法是使用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
类还支持进度报告和取消,并使用事件驱动的调度模式与调度线程,因此您不必担心volatile变量等...
修改强>
以下是更详细的示例,包括取消和进度报告:
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()
。请注意,这是完全由用户处理的取消机制(它不支持线程中止或类似于开箱即用的任何内容)。
答案 1 :(得分:21)
你可以保持一个挥发性的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;
}
}
答案 2 :(得分:8)
您可以在已用完的回调中禁用和启用计时器。
public void TimerElapsed(object sender, EventArgs e)
{
_timer.Stop();
//Do Work
_timer.Start();
}
答案 3 :(得分:5)
您可以使用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;
}
答案 4 :(得分:4)
使用帖子here
中的PeriodicTaskFactoryCancellationTokenSource 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 . . .
<强> 更新 强>
如果您希望使用PeriodicTaskFactory的“跳过事件”行为,请不要使用同步选项并实现Monitor.TryEnter,就像Bob在此处所做的那样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
的好处是返回了一个可以与所有TPL API一起使用的Task,例如: Task.Wait
,延续等等。
答案 5 :(得分:3)
您可以在任务完成之前停止计时器,并在任务完成后再次启动计时器,这可以使你的表演在平均时间间隔内进行。
public void myTimer_Elapsed(object sender, EventArgs e)
{
myTimer.Stop();
// Do something you want here.
myTimer.Start();
}
答案 6 :(得分:3)
如果您希望计时器的回调在后台线程上触发,则可以使用System.Threading.Timer。此Timer类允许您“指定Timeout.Infinite
以禁用定期信令。”作为constructor的一部分,导致计时器仅触发一次。
然后,当第一个计时器的回调触发并完成时,您可以构造一个新的计时器,防止多个计时器被安排,直到您准备好它们为止。
这里的优点是你不会创建计时器,然后反复取消它们,因为你从来没有安排过多次“下一次事件”。
答案 7 :(得分:3)
至少有20种不同的方法可以实现这一点,从使用计时器和信号量,到易变量变量,到使用TPL,再到使用Quartz等开源调度工具。
创建一个线程是一项昂贵的练习,所以为什么不创建一个并让它在后台运行,因为它将花费大部分时间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);
}
答案 8 :(得分:3)
您可以使用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);
}
答案 9 :(得分:3)
为什么不使用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);
}
}
答案 10 :(得分:2)
如果我理解正确,你实际上只是想在发送另一个线程之前确保你的线程没有运行。假设您在类中定义了一个线程,就像这样。
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
}
}
答案 11 :(得分:2)
这个问题已经有很多好的答案,包括基于TPL中某些功能的a slightly newer one。但我觉得这里缺乏:
async
/ await
来实现时序单个方法中的机制,以及c)引用的实现相当复杂,这有点模糊了这个特定问题的基础相关点。Task
和async
/ await
来实现定期行为,因为它简化了代码。 async
/ await
功能特别是所以在获取代码时非常有用,否则这些代码会被延续/回调实现细节破坏,并保留其自然的线性逻辑。单一方法。但这里没有答案证明这种简单。所以,基于这个理由激励我为这个问题添加另一个答案......
对我来说,首先要考虑的是“这里需要什么确切的行为?”这里的问题从一个基本前提开始:即使任务花费的时间超过计时器周期,计时器启动的周期任务也不应同时运行。但是,有多种方式可以实现前提,包括:
根据评论,我的印象是#3选项与OP的原始请求最匹配,但听起来#1选项可能也会起作用。但是选项#2和#4可能比其他人更可取。
在下面的代码示例中,我使用五种不同的方法实现了这些选项(其中两种方法实现了选项#3,但方式略有不同)。当然,可以根据需要选择合适的实施方案。你可能不需要所有五个一个程序! :)
关键在于,在所有这些实现中,它们自然而且以非常简单的方式,以周期但非并发的方式执行任务。也就是说,它们有效地实现了基于计时器的执行模型,同时确保每个主题仅根据问题的主要请求执行任务。
此示例还说明了CancellationTokenSource
如何用于中断句点任务,利用await
以简洁明了的方式处理基于异常的模型。
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;
}
}
}
最后一点注意事项:我在跟随它作为另一个问题的副本之后遇到了这个Q&amp; A.在另一个问题中,与此不同的是,OP特别指出他们正在使用System.Windows.Forms.Timer
类。当然,这个类的使用主要是因为它具有在UI线程中引发Tick
事件的好功能。
现在,它和这个问题都涉及一个实际在后台线程中执行的任务,因此该定时器类的UI线程关联行为在这些场景中并不是特别有用。这里的代码是为了匹配“启动后台任务”范例而实现的,但它可以很容易地进行更改,以便直接调用taskAction
委托,而不是在Task
中运行并等待。除了我上面提到的结构优势之外,使用async
/ await
的好处是它保留了System.Windows.Forms.Timer
类所需的线程关联行为。
答案 12 :(得分:1)
这应该做你想要的。它执行一个线程,然后加入线程直到它完成。进入一个计时器循环,以确保它没有过早地执行一个线程,然后再次关闭并执行。
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!");
}
}
}
应该产生类似的东西:
定时器功能! 睡了一会儿! 好的,进入另一个线程创建! 在定时器功能! 睡了一会儿! 好的,进入另一个线程创建! 在定时器功能! ... ...
希望这有帮助! SR
答案 13 :(得分:1)
这是ExecuteTaskCallback
方法的内容。这个位负责做一些工作,但前提是它还没有这样做。为此,我使用了ManualResetEvent
(canExecute
),最初设置为在StartTaskCallbacks
方法中发出信号。
请注意我使用canExecute.WaitOne(0)
的方式。零意味着WaitOne
将立即返回WaitHandle
(MSDN)的状态。如果省略零,那么最终每次调用ExecuteTaskCallback
最终都会运行任务,这可能是相当灾难性的。
另一个重要的事情是能够彻底结束处理。我选择阻止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();
}
答案 14 :(得分:1)
前段时间我遇到了同样的问题,我所做的就是使用lock{}语句。有了这个,即使Timer想要做任何事情,他也不得不等待,直到Lock-Block结束。
即
lock
{
// this code will never be interrupted or started again until it has finished
}
这是一个很好的方法,确保您的流程能够在不中断的情况下一直工作。
答案 15 :(得分:0)
我建议使用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; }
}
}
}
如果有帮助,请告诉我。