23

那么您认为防止 C# Windows 服务的多个线程同时运行的最佳方法是什么(该服务正在使用timer事件OnElapsed)?

使用lock()mutex

我似乎无法掌握 的概念mutex,但 usinglock()似乎对我的情况很好。

我应该花时间学习如何使用这些mutex东西吗?

4

6 回答 6

68

使您的计时器成为一次性的,并在经过的事件处理程序中重新初始化它。例如,如果你使用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
}

这样做的缺点是计时器不会以一秒的间隔触发。相反,它会在最后一个滴答的处理完成后一秒触发。

于 2012-05-04T03:00:26.587 回答
11

不要使用计时器来生成线程。只启动一个线程。当线程完成一个工作周期时,计算下一个周期应该开始之前的剩余时间。如果此时间间隔为 0 或负数,则立即循环返回并开始一个新的循环,如果为正数,则在循环返回之前休眠该时间间隔。

这通常是通过在结束滴答声和开始滴答声之间获取无符号整数减法的 int 结果来完成的,因此给出了工作所采用的经过的滴答声。从所需的时间间隔中减去这个,就得到了新的剩余时间。

不需要额外的计时器线程,不可能同时运行两个线程,简化的整体设计,没有连续的创建/启动/终止/销毁,没有 mallocs,没有 new(),没有堆栈分配/释放,没有 GC。

其他使用定时器、互斥体、信号量、锁等的设计过于复杂。如果不制作任何额外的线程更容易和更简单,为什么还要尝试用同步来停止额外的线程呢?

有时,使用计时器而不是 sleep() 循环是一个非常糟糕的主意。这听起来像是其中之一。

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);
    }
}
于 2012-05-04T01:33:09.363 回答
6

如果回调已被另一个计时器线程执行,您可以使用Monitor.TryEnter()代替lock来返回:

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);
        }
    }
}
于 2015-06-25T18:31:17.463 回答
3

我想我知道你想做什么。您有一个定期执行回调的计时器(计时器的定义),并且该回调做了一些工作。那一点工作实际上可能需要比计时器周期更多的时间(例如,计时器周期是 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
});
于 2012-05-04T03:08:40.920 回答
2

如果您只想防止同一进程/应用程序域中的两个线程同时执行,那么该lock语句可能会为您做。

但请注意,这lock会使其他线程在等待访问临界区时处于锁定状态。它们不会被中止或重定向或任何东西;他们坐在那里,等待原始线程完成lock块的执行,以便他们可以运行。

Amutex会给你更大的控制权;包括使第二个和后续线程完全停止的能力,而不是锁定和锁定跨进程的线程。

于 2012-05-04T01:05:08.107 回答
1

我认为其中一些方法很棒,但有点复杂。

我创建了一个包装类,它可以防止计时器重叠,并允许您选择是“每个 INTERVAL 应该调用一次 ELAPSED”还是“调用之间应该发生一个 INTERVAL 延迟”。

如果您改进了此代码,请在此处发布更新!

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

    }
}
于 2015-07-16T23:10:36.753 回答