Подтвердить что ты не робот

Предотвратить множественный экземпляр службы - лучший подход?

Как вы думаете, лучший способ предотвратить одновременное выполнение нескольких потоков службы С# Windows (служба использует timer с событием OnElapsed)?

Используя lock() или mutex?

Я не могу понять концепцию mutex, но использование lock(), похоже, отлично работает для моего случая.

Должен ли я тратить время на изучение того, как использовать mutex в любом случае?

4b9b3361

Ответ 1

Сделайте свой таймер одним выстрелом и повторно инициализируйте его в обработчике прошедшего события. Например, если вы используете 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
}

Недостатком этого является то, что таймер не срабатывает с интервалом в одну секунду. Скорее, он срабатывает через секунду после завершения последней обработки тика.

Ответ 2

Не используйте таймер для создания потоков. Только начинайте один поток. Когда поток завершит рабочий цикл, подсчитайте, сколько времени осталось до начала следующего цикла. Если этот интервал равен 0 или отрицательный, немедленно вернитесь назад и начните новый цикл, если он положительный, спящий для этого интервала перед тем, как вернуться назад.

Обычно это делается путем принятия int-результата беззнакового int-вычитания между финишными тиками и началом тиков, поэтому давая истекшие тики, сделанные работой. Вычитание этого из требуемого интервала дает оставшееся время.

Нет необходимости в дополнительном потоке таймера, нет возможности одновременного запуска двух потоков, упрощенного общего дизайна, непрерывного создания/запуска/завершения/уничтожения, без mallocs, no 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);
    }
}

Ответ 3

Вместо блокировки вы можете использовать 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);
        }
    }
}

Ответ 4

Если вы хотите предотвратить одновременное выполнение двух потоков в одном домене процесса/приложения, оператор lock, вероятно, сделает для вас.

Но учтите, что lock оставляет другие потоки, ну, они блокируются, пока они ждут доступа к критическому разделу. Они не прерываются или не перенаправляются или что-либо еще; они сидят там, ожидая, когда исходный поток завершит выполнение блока lock, чтобы они могли работать.

A mutex даст вам больший контроль; в том числе возможность иметь второй и последующий потоки, просто останавливаются вообще, а не блокировать и блокировать потоки между процессами.

Ответ 5

Думаю, я знаю, что вы пытаетесь сделать. У вас есть таймер, который периодически выполняет обратный вызов (определение таймера), и этот обратный вызов выполняет немного работы. этот бит работы может занимать больше времени, чем период таймера (например, период таймера составляет 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
});

Ответ 6

Я думаю, что некоторые из этих подходов являются фантастическими, но немного сложными.

Я создал класс-оболочку, который предотвращает перекрытие таймера и позволяет вам выбрать, следует ли вызывать "ELAPSED" после каждого INTERVAL "или" Задержка 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

    }
}