【问题标题】:Non-reentrant timers不可重入定时器
【发布时间】:2011-08-14 08:31:04
【问题描述】:

我有一个函数,我想每 x 秒调用一次,但我希望它是线程安全的。

我可以在创建计时器时设置此行为吗? (我不介意我使用哪个 .NET 计时器,我只是希望它是线程安全的)。

我知道我可以在我的回调函数中实现锁,但我认为如果它在计时器级别会更优雅。

我的回调函数和环境与 UI 无关。

[编辑 1] 我只是不希望我的回调函数中有多个线程。

[编辑 2] 我想将锁定保持在计时器级别,因为计时器负责何时调用我的回调,而这里有一种特殊情况,我不想调用我的回调函数。所以我认为何时调用是计时器的责任

【问题讨论】:

  • 您没有提供足够的信息...您的函数是否访问程序的任何其他部分访问的任何内容?如果不是,那么它已经是线程安全的(有和没有定时器)......如果是,那么定时器没有线程安全,但你需要使用一些锁等。
  • 为什么你想让回调函数完全锁定?
  • 您的编辑 2 没有意义:计时器负责按时调用您的函数,您的函数负责正确处理您的共享数据(锁定等) - 时间不能对您的数据负责,您的职能是……
  • 计时器本身线程安全的 - 你的代码可能不是,但给出的信息不足以回答如何使其成为线程安全的。

标签: c# timer thread-safety


【解决方案1】:

我猜测,由于您的问题并不完全清楚,您希望确保在处理回调时您的计时器无法重新输入您的回调,并且您希望在不锁定的情况下执行此操作。您可以使用System.Timers.Timer 并确保AutoReset 属性设置为false 来实现此目的。这将确保您必须在每个间隔手动触发计时器,从而防止任何重入:

public class NoLockTimer : IDisposable
{
    private readonly Timer _timer;

    public NoLockTimer()
    {
        _timer = new Timer { AutoReset = false, Interval = 1000 };

        _timer.Elapsed += delegate
        {
            //Do some stuff

            _timer.Start(); // <- Manual restart.
        };

        _timer.Start();
    }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Dispose();
        }
    }
} 

【讨论】:

  • 您完全理解我的问题,并且喜欢您的解决方案,我希望这种行为可以在计时器类中继承,但这已经足够了..
  • 这正是我所做的,而且效果很好。仅供参考 - 如果您不使用匿名委托,这个例子会更清楚一些。
【解决方案2】:

作为 Tim Lloyd 的 System.Timers.Timer 解决方案的补充,这里有一个解决方案可以防止在您想改用 System.Threading.Timer 的情况下重入。

TimeSpan DISABLED_TIME_SPAN = TimeSpan.FromMilliseconds(-1);

TimeSpan interval = TimeSpan.FromSeconds(1);
Timer timer = null; // assign null so we can access it inside the lambda

timer = new Timer(callback: state =>
{
  doSomeWork();
  try
  {
    timer.Change(interval, DISABLED_TIME_SPAN);
  }
  catch (ObjectDisposedException timerHasBeenDisposed)
  {
  }
}, state: null, dueTime: interval, period: DISABLED_TIME_SPAN);

我相信您不希望在回调内部访问 interval,但这很容易解决,如果您想:将上述内容放入包装 BCL 的 TimerNonReentrantTimer 类中班级。然后,您可以将 doSomeWork 回调作为参数传递。这种类的一个例子:

public class NonReentrantTimer : IDisposable
{
    private readonly TimerCallback _callback;
    private readonly TimeSpan _period;
    private readonly Timer _timer;

    public NonReentrantTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
    {
        _callback = callback;
        _period = period;
        _timer = new Timer(Callback, state, dueTime, DISABLED_TIME_SPAN);
    }

    private void Callback(object state)
    {
        _callback(state);
        try
        {
            _timer.Change(_period, DISABLED_TIME_SPAN);
        }
        catch (ObjectDisposedException timerHasBeenDisposed)
        {
        }
    }


    public void Dispose()
    {
        _timer.Dispose();
    }
}

【讨论】:

  • 不错的包装类,谢谢!一点点改进:已经有一个Timeout.InfiniteTimeSpan 静态字段。
【解决方案3】:

我知道我可以在我的回调函数中实现锁,但我认为如果它在计时器级别会更优雅

如果需要锁定,那么计时器如何安排呢?您正在寻找一个神奇的免费赠品。

重新编辑1:

您的选择是 System.Timers.Timer 和 System.Threading.Timer,两者都需要预防重入。请参阅this page 并查找处理定时器事件重入部分。

【讨论】:

  • 查看我的编辑 1,对您有意义吗?
  • @Hank 我读了你提到的部分,我喜欢你的回答,但是 chibacity 对我的问题给出了更合适的答案,因为我想在计时器设置中管理它......而不是进入逻辑在回调函数内,再次感谢
【解决方案4】:
using System;
using System.Diagnostics;

/// <summary>
///     Updated the code.
/// </summary>
public class NicerFormTimer : IDisposable {

    public void Dispose() {
        using ( this.Timer ) { }

        GC.SuppressFinalize( this );
    }

    private System.Windows.Forms.Timer Timer { get; }

    /// <summary>
    ///     Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
    /// </summary>
    /// <param name="action"></param>
    /// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
    /// <param name="milliseconds"></param>
    public NicerFormTimer( Action action, Boolean repeat, Int32? milliseconds = null ) {
        if ( action == null ) {
            return;
        }

        this.Timer = new System.Windows.Forms.Timer {
            Interval = milliseconds.GetValueOrDefault( 1000 )
        };

        this.Timer.Tick += ( sender, args ) => {
            try {
                this.Timer.Stop();
                action();
            }
            catch ( Exception exception ) {
                Debug.WriteLine( exception );
            }
            finally {
                if ( repeat ) {
                    this.Timer.Start();
                }
            }
        };

        this.Timer.Start();
    }

}

/// <summary>
///     Updated the code.
/// </summary>
public class NicerSystemTimer : IDisposable {

    public void Dispose() {
        using ( this.Timer ) { }

        GC.SuppressFinalize( this );
    }

    private System.Timers.Timer Timer { get; }

    /// <summary>
    ///     Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
    /// </summary>
    /// <param name="action"></param>
    /// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
    /// <param name="milliseconds"></param>
    public NicerSystemTimer( Action action, Boolean repeat, Double? milliseconds = null ) {
        if ( action == null ) {
            return;
        }

        this.Timer = new System.Timers.Timer {
            AutoReset = false,
            Interval = milliseconds.GetValueOrDefault( 1000 )
        };

        this.Timer.Elapsed += ( sender, args ) => {
            try {
                this.Timer.Stop();
                action();
            }
            catch ( Exception exception ) {
                Debug.WriteLine( exception );
            }
            finally {
                if ( repeat ) {
                    this.Timer.Start();
                }
            }
        };

        this.Timer.Start();
    }

}

【讨论】:

    【解决方案5】:

    计时器如何知道您的共享数据?

    定时器回调在某个 ThreadPool 线程上执行。所以你至少会有 2 个线程:

    1. 创建和启动计时器的主线程;
    2. 来自 ThreadPool 的线程用于启动回调。

    您有责任为您的共享数据提供正确的工作。

    重新编辑: chibacity 提供了一个完美的例子。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多