所以,你觉得是为了防止同时运行的C#Windows服务的多线程的最佳方法(该服务使用的timer
与OnElapsed
事件)?
使用lock()
或mutex
?
我似乎无法把握的概念mutex
,但使用lock()
似乎对我的情况很好地工作。
我应该花时间学习如何使用mutex
反正?
所以,你觉得是为了防止同时运行的C#Windows服务的多线程的最佳方法(该服务使用的timer
与OnElapsed
事件)?
使用lock()
或mutex
?
我似乎无法把握的概念mutex
,但使用lock()
似乎对我的情况很好地工作。
我应该花时间学习如何使用mutex
反正?
让你的计时器一次性的,它在过去的事件处理程序重新初始化。 例如,如果你使用System.Timers.Timer
,你会初始化它是这样的:
myTimer.Elapsed = timer1Elapsed;
myTimer.Interval = 1000; // every second
myTimer.AutoReset = false; // makes it fire only once
myTimer.Enabled = true;
而你经过的事件处理程序:
void timerElapsed(object source, ElapsedEventArgs e)
{
// do whatever needs to be done
myTimer.Start(); // re-enables the timer
}
这样做的缺点是计时器不会触发一秒的间隔。 相反,它激发了最后一跳的处理完成后一秒。
不要使用定时器来生成线程。 永远只启动一个线程。 当线程完成一个工作循环,计算多久仍先下一周期应启动。 如果在此间隔为0或负,环回立即循环回到之前启动一个新的周期,如果是阳性,睡眠该时间间隔。
这通常是通过取一个unsigned int相减的结果INT结束蜱和蜱开始之间,所以给出了由工作所采取的已过蜱完成。 从所需的时间间隔中减去该给剩余的新时间。
无需额外的计时器线程,没有两个线程可能同时运行,简化了整体设计,没有持续创建/启动/停止/销毁,没有mallocs,没有新的(),没有堆栈分配/释放分配没有GC。
使用定时器,互斥,信号锁等其他设计都只是在复杂。 为什么不厌其烦地尝试停止与同步的额外的线程,如果它仅仅是容易和简单没有任何额外的线程?
有时,使用定时器来代替睡眠()循环的仅仅是一个非常糟糕的主意。 这听起来像这样的一个时期。
public void doWorkEvery(int interval)
{
while (true)
{
uint startTicks;
int workTicks, remainingTicks;
startTicks = (uint)Environment.TickCount;
DoSomeWork();
workTicks=(int)((uint)Environment.TickCount-startTicks);
remainingTicks = interval - workTicks;
if (remainingTicks>0) Thread.Sleep(remainingTicks);
}
}
取而代之的锁 ,你可以使用Monitor.TryEnter()如果回调已被另一计时器线程执行返回:
class Program
{
static void Main(string[] args)
{
Timer t = new Timer(TimerCallback, null,0,2000);
Console.ReadKey();
}
static object timerLock = new object();
static void TimerCallback(object state)
{
int tid = Thread.CurrentThread.ManagedThreadId;
bool lockTaken = false;
try
{
lockTaken = Monitor.TryEnter(timerLock);
if (lockTaken)
{
Console.WriteLine("[{0:D02}]: Task started", tid);
Thread.Sleep(3000); // Do the work
Console.WriteLine("[{0:D02}]: Task finished", tid);
}
else
{
Console.WriteLine("[{0:D02}]: Task is already running", tid);
}
}
finally
{
if (lockTaken) Monitor.Exit(timerLock);
}
}
}
如果你想要的是防止在同一进程/应用程序域两个线程并发执行,该lock
语句可能会为你做。
但要注意, lock
离开其他线程,很好, 锁定而他们等待进入临界区。 他们不是中止或重定向或任何东西; 他们坐在那里,等待原来的线程来完成的执行lock
块,它们可以运行。
一个mutex
会给你更大的控制权; 包括有第二次及以后的线程只是完全停止,而不是锁定的能力,以及跨进程锁螺纹。
我想我知道你想要做什么。 你有定期执行的回调(如定时器的定义)的定时器和回调做了一些工作。 工作的该位可能实际上会比定时器周期更长的时间(例如定时器周期为500毫秒,回调的给定调用可能需要更长的时间是500毫秒)。 这意味着,你的回调必须重入。
如果您不能重入(并有各种原因,这可能是); 我已经在过去做的是在回调的开始关闭计时器,然后在年底重新打开它。 例如:
private void timer_Elapsed(object source, ElapsedEventArgs e)
{
timer.Enabled = false;
//... do work
timer.Enabled = true;
}
如果你想真正想要一个“线程”接连立即执行,我不会建议使用定时器; 我建议使用Task对象。 例如
Task.Factory.StartNew(()=>{
// do some work
})
.ContinueWith(t=>{
// do some more work without running at the same time as the previous
});
我认为,其中一些方法是太棒了,但有点复杂。
我创建了一个包装类,防止重叠的计时器,并允许您选择是否“经历的每个时间间隔应该被称为一次”或“间隔延迟应该调用之间发生”。
如果你对这个代码改进,请在这里发表的更新!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AllCommander.Diagnostics {
public class SafeTimer : IDisposable {
public enum IntervalStartTime {
ElapsedStart,
ElapsedFinish
}
private System.Timers.Timer InternalTimer;
public bool AutoReset { get; set; }
public bool Enabled {
get {
return InternalTimer.Enabled;
}
set {
if (value) {
Start();
} else {
Stop();
}
}
}
private double __Interval;
public double Interval {
get {
return __Interval;
}
set {
__Interval = value;
InternalTimer.Interval = value;
}
}
/// <summary>
/// Does the internal start ticking at the END of Elapsed or at the Beginning?
/// </summary>
public IntervalStartTime IntervalStartsAt { get; set; }
public event System.Timers.ElapsedEventHandler Elapsed;
public SafeTimer() {
InternalTimer = new System.Timers.Timer();
InternalTimer.AutoReset = false;
InternalTimer.Elapsed += InternalTimer_Elapsed;
AutoReset = true;
Enabled = false;
Interval = 1000;
IntervalStartsAt = IntervalStartTime.ElapsedStart;
}
void InternalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
if (Elapsed != null) {
Elapsed(sender, e);
}
var ElapsedTime = DateTime.Now - e.SignalTime;
if (AutoReset == true) {
//Our default interval will be INTERVAL ms after Elapsed finished.
var NewInterval = Interval;
if (IntervalStartsAt == IntervalStartTime.ElapsedStart) {
//If ElapsedStart is set to TRUE, do some fancy math to determine the new interval.
//If Interval - Elapsed is Positive, then that amount of time is remaining for the interval
//If it is zero or negative, we're behind schedule and should start immediately.
NewInterval = Math.Max(1, Interval - ElapsedTime.TotalMilliseconds);
}
InternalTimer.Interval = NewInterval;
InternalTimer.Start();
}
}
public void Start() {
Start(true);
}
public void Start(bool Immediately) {
var TimerInterval = (Immediately ? 1 : Interval);
InternalTimer.Interval = TimerInterval;
InternalTimer.Start();
}
public void Stop() {
InternalTimer.Stop();
}
#region Dispose Code
//Copied from https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/
bool _disposed;
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
~SafeTimer() {
Dispose(false);
}
protected virtual void Dispose(bool disposing) {
if (!_disposed) {
if (disposing) {
InternalTimer.Dispose();
}
// release any unmanaged objects
// set the object references to null
_disposed = true;
}
}
#endregion
}
}