那么,您认为如何防止多个线程同时运行 C# Windows 服务的最佳方法(该服务使用带有 OnElapsed 事件的定时器)?
使用 lock()
还是 mutex
?
我似乎无法理解 mutex
的概念,但是对于我的情况,使用 lock()
看起来很好用。
我应该花时间学习如何使用 mutex
吗?
那么,您认为如何防止多个线程同时运行 C# Windows 服务的最佳方法(该服务使用带有 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
}
这样做的缺点是计时器不会在每秒钟的间隔上触发,而是在上一次处理完成后一秒钟触发。System.Threading.Timer
,则会将最后一个参数初始化为Timeout.Infinite
。并且在回调中,您将调用带有最后一个参数等于Timeout.Infinite
的Change
函数。 - Jim Mischelpublic 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);
}
}
Sleep
。因此,最后一行变成了Thread.Sleep(Math.Max(remainingTicks, MINIMUM_TICKS));
,而不是if..Sleep..
。原因1:始终让出CPU,以防有另一个线程具有相同的优先级。可以将MINIMUM_TICKS设置为0以获得简短的让出。原因2:如果该任务重复花费太长时间,则避免消耗太多CPU。在这种情况下,选择MINIMUM_TICKS作为我们希望保证该任务在每个周期中都不占用任何CPU的时间量。 - ToolmakerSteveuint
。 int
数学会以这样的方式“包装”,只有在开始和结束时间之间的差异超过int.MaxValue时才需要使用它(在这种情况下,您应该改用ulong
和GetTickCount64
)。 实际上,使用uint
使减法变得相当奇怪:即使结果可能是负值,您也正在取两个无符号值的差异。这是荒谬的。它之所以起作用,仅因为您将其重新转换为int
。最好一开始就全部使用int
。无论哪种方式,都必须允许“溢出”。 - ToolmakerSteve你可以使用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);
}
}
}
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
});
lock
语句可能适合你。lock
会将它们锁定在那里。它们不会被中止、重定向或其他任何操作;它们只是坐在那里,等待原始线程完成对lock
块的执行,以便它们可以运行。mutex
可以给您更多的控制权,包括能够使第二个及后续线程完全停止而不是锁定,并且可以锁定跨进程的线程。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
}
}
e.SignalTime
,以便时间测量准确。顺便说一句,我会将处理程序命名为OnElapsed
或ElapsedHandler
,而不是Elapsed
(对我来说听起来像是时间测量)。 - ToolmakerSteve