为什么当Enabled设置为false时,System.Timer.Timer仍会激发事件
本文关键字:Timer 事件 System Enabled 设置 false 为什么 | 更新日期: 2023-09-27 18:00:16
我尝试创建Timer的派生类,该类允许设置"Pause"锁存,以防止工作线程重新激活计时器。但是,当AutoReset设置为false时,将继续引发Elapsed事件,并且一旦设置了Paused变量,Enabled访问器似乎正在执行其工作,以防止基类的Enabled属性被修改。
为什么会发生这种情况,或者我应该使用什么策略来进一步了解这里实际发生了什么互动?
我在下面附上了派生类的实现。
using System.Timers
class PauseableTimer : Timer
{
public bool Paused;
new public bool Enabled
{
get
{
return base.Enabled;
}
set
{
if (Paused)
{
if (!value) base.Enabled = false;
}
else
{
base.Enabled = value;
}
}
}
}
举例说明问题。
class Program
{
private static PauseableTimer _pauseableTimer;
private static int _elapsedCount;
static void Main(string[] args)
{
_pauseableTimer = new PauseableTimer(){AutoReset = false,Enabled = false,Paused = false};
_pauseableTimer.Elapsed += pauseableTimer_Elapsed;
_pauseableTimer.Interval = 1;
_pauseableTimer.Enabled = true;
while(_elapsedCount<100)
{
if (_elapsedCount > 50) _pauseableTimer.Paused = true;
}
}
static void pauseableTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine(String.Format("this.Enabled:'{0}',Paused:'{1}',AutoReset:'{2}",_pauseableTimer.Enabled,_pauseableTimer.Paused,_pauseableTimer.AutoReset));
_elapsedCount++;
_pauseableTimer.Interval = _pauseableTimer.Interval == 1 ? 2 : 1; //This line breaks it.
_pauseableTimer.Enabled = true;
}
}
相关文档,System.Timers.Timer.Interval
备注如果Enabled和AutoReset都设置为false,并且计时器以前已启用,则设置Interval属性会引发Elapsed事件一次,就好像Enabled属性已设置为true一样。若要在不引发事件的情况下设置间隔,可以将"自动重置"属性临时设置为true。
建议的将AutoReset
设置为true的解决方案并不能解决问题,因为在允许触发事件的事件处理程序期间,存在将AutoReset
设置为true这一未记录的行为。
解决方案似乎是构建派生对象,这样就可以避免事件再次触发的许多明显方式中的任何一种。
以下是我结束时的实现。
public class PauseableTimer : Timer
{
private bool _paused;
public bool Paused
{
get { return _paused; }
set
{
Interval = _interval;
_paused = value;
}
}
new public bool Enabled
{
get
{
return base.Enabled;
}
set
{
if (Paused)
{
if (!value) base.Enabled = false;
}
else
{
base.Enabled = value;
}
}
}
private double _interval;
new public double Interval
{
get { return base.Interval; }
set
{
_interval = value;
if (Paused){return;}
if (value>0){base.Interval = _interval;}
}
}
public PauseableTimer():base(1){}
public PauseableTimer(double interval):base(interval){}
}
恐怕多线程中的一切都更复杂。假设您的代码按您的意愿工作,那么在重置Enabled
属性后,可以在一个窗口中引发运行中的事件。请参阅MSDN文档中的这句话。
引发Elapsed事件的信号总是排队等待在上执行ThreadPool线程。这可能会导致在引发的Elapsed事件中在Enabled属性设置为之后false。Stop的代码示例方法显示了一种解决方法这种比赛条件。
另一个选项是抑制事件???我无法解释发生了什么,但下面给出的理论应该可以让你绕过你讨论过的这个小问题。正如Steve所提到的,在您正在尝试设置的"已启用属性上设置观察和断点",并确保它确实正在设置中。
我该如何解决此问题:
捕获并检查"Enabled"属性,并在需要时删除订阅方法(处理程序)的"-=",然后在确实需要处理"Elapsed"事件时再次添加它的"+="。
我已经在一些不同的WinForms项目中多次使用这种风格。如果不希望以编程方式处理"Elapsed"事件,请在满足特定条件时创建一个检查并删除它,然后在满足相反条件时添加它。
if (paused) // determine pause logic to be true in here
{
timer.Elapsed -= ... // remove the handling method.
}
else
{
timer.Elapsed += ... // re-add it in again
}
上面的代码逻辑将允许您在"Paused"标志为true时,每次引发"Elapsed"事件时都可以忽略该事件。我希望以上内容能帮助
在System.Timers.Timer中,创建类时会将Elapsed事件添加到ThreadPool中。之后它被发射。此时Enabled属性可能为false。您对此无能为力,但您可以做的是在Elapsed事件触发时测试Enabled属性是否为true。我确实覆盖了Enabled属性来实现这一神奇,作为额外的功能,我还在其中放入了一个IsDisposed属性:
public class DisposableTimer : System.Timers.Timer {
/// <summary>
/// override the Timer base class Enabled property
/// </summary>
/// <remarks>
/// the code in the Elapsed event should only be executed when the Enabled property is set to "true".
/// we cannot prevent that the Elapsed event is fired at the start, because its automatically put in the ThreadPool,
/// but we can prevent that the code in it can be executed when the Enabled property is "false".
/// </remarks>
private bool enabled;
public new bool Enabled
{
get
{
return enabled;
}
set
{
enabled = base.Enabled = value;
}
}
/// <summary>
/// count the heartbeats
/// </summary>
public int HeartbeatCounter { get; set; }
/// <summary>
/// name of timer
/// </summary>
public string TimerName { get; set; }
/// <summary>
/// show heartbeat on console
/// </summary>
public bool ShowHeartBeat { get; set; }
// type of entry in eventlog
public EventLogEntryType EventLogEntryType { get; set; }
// updated interval to process messages
public Func<double> UpdatebleInterval { get; set; }
/// <summary>
/// used eventlog
/// </summary>
public EventLog EventLog { get; set; }
/// <summary>
/// message logging
/// </summary>
/// <remarks>
/// this property needs to be dynamic because in
/// pda service a different class is used but luckily :-)
/// with the same method for adding loggings.
/// </remarks>
public dynamic MessageLogging { get; set; }
/// <summary>
/// make sure there are no overlapping set of timer callbacks
/// </summary>
private object locker;
/// <summary>
/// initialize timer class
/// </summary>
/// <param name="actions">action to perform</param>
/// <param name="timerName">name of timer</param>
public DisposableTimer(List<Action> actions, string timerName) : base()
{
// used to make sure there are no overlapping executing sets of timer callbacks
locker = new object();
// wrapper for the actions that need to be performed.
base.Elapsed += (s, a) => Callback(actions);
// set name of timer
this.TimerName = timerName;
/*
* only once a callback is executed after elapsed time,
* because there is only one callback executed there can be
* no overlap, because the "reset" is done after the set of
* callbacks are executed.
*/
AutoReset = false;
// timer is not started yet
Enabled = false;
}
/// <summary>
/// check if verwijder bericht timer is disposed
/// </summary>
public bool IsDisposed
{
get
{
var timerType = typeof(System.Timers.Timer);
var timerDisposedField = timerType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
return (bool)timerDisposedField.GetValue(this);
}
}
/// <summary>
/// after a callback a timer needs to be reset to continue running if AutoReset=false.
/// </summary>
/// <param name="interval">new interval of timer</param>
private void Reset(double interval)
{
// stop the timer
Stop();
// only do when not disposed yet.
if (!IsDisposed)
{
// adjust interval if needed
if (interval != 0)
Interval = interval;
// release exclusive lock
Monitor.Exit(locker);
}
// start the timer again
Start();
}
/// <summary>
/// only start if not disposed and started yet
/// </summary>
public new void Start()
{
if (!IsDisposed && !Enabled)
Enabled = true;
}
/// <summary>
/// only stop if not disposed and stopped yet
/// </summary>
public new void Stop()
{
if (!IsDisposed && Enabled)
Enabled = false;
}
/// <summary>
/// set of callbacks to perform after timer elapse interval
/// </summary>
/// <param name="callBackActions">list of callbacks inside this wrapper to execute</param>
public void Callback(List<Action> callBackActions)
{
// only execute callbacks if timer is enabled.
if (Enabled)
{
/*
* AutoReset = false, so a callback is only executed once,
* because of this overlapping callbacks should not occur,
* but to be sure exclusive locking is also used.
*/
var hasLock = false;
// show heartbeat at output window
if (ShowHeartBeat)
Debug.WriteLine(string.Format("HeartBeat interval: {0}...{1}/thread: 0x{2:X4}", TimerName, ++HeartbeatCounter, AppDomain.GetCurrentThreadId() ));
// execute callback action.
try
{
// only perform set of actions if not executing already on this thread.
Monitor.TryEnter(locker, ref hasLock);
if (hasLock)
{
// show heartbeat at output window
if (ShowHeartBeat)
Debug.WriteLine(string.Format(" action: {0}...{1}/thread: 0x{2:X4}", TimerName, HeartbeatCounter, AppDomain.GetCurrentThreadId()));
// execute the set of callback actions
foreach (Action callBackAction in callBackActions)
{
// execute callback
try
{
callBackAction();
}
// log error, but keep the action loop going.
catch (Exception ex)
{
EventLog.WriteEntry(ex.Message, EventLogEntryType.Warning);
MessageLogging.Insert(ex.Message);
}
}
}
// show that action is busy
else if (ShowHeartBeat)
Debug.WriteLine(string.Format(" busy: {0}...{1}/thread: 0x{2:X4}", TimerName, HeartbeatCounter, AppDomain.GetCurrentThreadId()));
}
// adjust interval when needed and release exclusive lock when done.
finally
{
// after the complete action is finished the lock should be released.
if (hasLock)
{
// timer interval can be changed when timer is active, callback function is needed for this.
double newInterval = 0;
if (UpdatebleInterval != null)
{
// calculate new interval for timer
double updatedInterval = UpdatebleInterval();
if (Interval != updatedInterval)
{
// based on Dutch
var dutchCultureInfo = new CultureInfo("nl-NL", false).TextInfo;
// write interval change to loggings
string intervalMessage = dutchCultureInfo.ToTitleCase(string.Format(@"{0} interval veranderd van {1} naar {2} seconden", TimerName, Interval / 1000, updatedInterval / 1000));
EventLog.WriteEntry(intervalMessage, EventLogEntryType.Information);
MessageLogging.Insert(intervalMessage);
// set for new interval
newInterval = updatedInterval;
}
}
// make ready for new callback after elapsed time, lock can be released by now.
Reset(newInterval);
}
}
}
// show heartbeat at output window
else if (ShowHeartBeat)
Debug.WriteLine(string.Format("HeartBeat thread: {0}...{1}/thread: 0x{2:X4}", TimerName, ++HeartbeatCounter, AppDomain.GetCurrentThreadId()));
}
}
我会重新格式化您的代码:
// from this
if (!value) base.Enabled = false;
// to this
if (!value)
base.Enabled = false;
它不仅读起来更好,你还可以在关键行上放一个断点,看看它是否正在执行