1

我正在使用System.Timers.Timer并且每隔 x 秒我需要在 ElapsedEvent 方法中执行一些任务。当我在 ElapsedEvent 方法中执行任务时,我希望停止计时器。但是,我有另一种可以启动计时器的方法,可以在 ElapsedEvent 运行时调用它。我的代码看起来像这样:

class MyClass {
   Timer myTimer;

   public MyClass {
      myTimer = new System.Timers.Timer();
      // init timer code here...
   }

   public void ElapsedEventTask(object source, ElapsedEventArgs e) {
      myTimer.Enabled = false;
      try
      {
          // do my tasks
      }
      catch
      {
         ...
      }
      finally
      {
         myTimer.Enabled = true;
      }
   }
}

public void AnotherMethod() {
   // do some things
   myTimer.Enabled = true;
}

如何防止AnotherMethod在完成任务时启动计时器ElapsedEventTask

4

3 回答 3

1

您可以添加一个变量来指示任务是否正在运行。lock最后是线程安全的,当这个变量用于 with 时,你需要使用myTimer.Enabled

class MyClass
{
    object syncEnableRunning = new object();
    bool running
    Timer myTimer;

    public void ElapsedEventTask(object source, ElapsedEventArgs e)
    {
        lock(syncEnableRunning)
        {
            running = true;
            myTimer.Enabled = false;
        }
        
        try { /*do my tasks*/}
        catch { ... }
        finally
        {
            lock(syncEnableRunning)
            {
                myTimer.Enabled = true;
                running = false;
            }
        }
    }

    public void AnotherMethod()
    {
        // do some things
        lock(syncEnableRunning)
        {
            if(!running)
            {
                myTimer.Enabled = true;
            }
        }
    }
}
于 2021-04-30T08:31:03.980 回答
0

根据文档System.Timers.Timer该类不是线程安全的,因此在Enabled没有同步的情况下从多个线程触摸其属性是不安全的(这样做会导致未定义的行为)。Vernou 的回答显示了如何使用锁来同步线程,但我个人对尝试使用显然被设计为可重入的机制来强制执行非重叠执行策略感到有点紧张。所以我的建议是放弃System.Timers.Timer, 并改用一个异步循环,由 Stephen Cleary 的PauseTokenSource机制控制:

class MyClass
{
    private readonly CancellationTokenSource _cts;
    private readonly PauseTokenSource _pts;
    public Task Completion { get; private set; }

    public MyClass(TimeSpan interval)
    {
        _cts = new CancellationTokenSource();
        _pts = new PauseTokenSource();
        _pts.IsPaused = true;
        Completion = Task.Run(async () =>
        {
            try
            {
                while (true)
                {
                    await _pts.Token.WaitWhilePausedAsync(_cts.Token);
                    var delayTask = Task.Delay(interval, _cts.Token);
                    /* Do my tasks */
                    await delayTask;
                }
            }
            catch (OperationCanceledException)
                when (_cts.IsCancellationRequested) { } // Ignore
        });
    }

    public void Start() => _pts.IsPaused = false;
    public void Stop() => _pts.IsPaused = true;
    public void Complete() => _cts.Cancel();
}

PauseTokenSource是 a 的控制器,与/组合PauseToken类似的概念。不同之处在于只能取消一次,而可以多次暂停/取消暂停。此类包含在AsyncEx.Coordination包中。CancellationTokenSourceCancellationTokenCancellationTokenSourcePauseTokenSource

MyClass公开了一个终止异步循环的Complete方法,以及一个Completion可以等待的属性。在关闭程序之前设置此属性是一个好主意await,以便为任何活动操作提供完成的机会。否则,该进程可能会在后台执行过程中被终止,从而产生不可预测的后果。

于 2021-04-30T17:03:50.773 回答
-1

我会创建一个一次性计时器,然后您需要在计时器功能结束时重新开始。

myTimer = new System.Timers.Timer();
myTimer.AutoReset = false;


public void ElapsedEventTask(object source, ElapsedEventArgs e) {
  ...
  finally
  {
     myTimer.Start();
  }

}

于 2021-04-30T08:01:22.387 回答