不可重入定时器

发布于 2024-11-29 18:18:30 字数 338 浏览 0 评论 0原文

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

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

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

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

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

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

I have a function that I want to invoke every x seconds, but I want it to be thread-safe.

Can I set up this behavior when I am creating the timer? (I don't mind which .NET timer I use, I just want it to be thread-safe).

I know I can implement locks inside my callback function, but I think it would be more elegant if it were in the timer level.

My callback function, and environment are not related to a UI.

[Edit 1]
I just don't want there to be more than one thread inside my callback function.

[Edit 2]
I want to keep the locking inside the timer level, because the timer is responsible for when to call my callback, and here there is a particular situation when I don't want to call my callback function. So I think when to call is the responsibility of the timer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

待天淡蓝洁白时 2024-12-06 18:18:30

我猜测,由于您的问题并不完全清楚,您希望确保您的计时器在处理回调时无法重新输入回调,并且您希望在不锁定的情况下执行此操作。您可以使用 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();
        }
    }
} 

I'm guessing, as your question is not entirely clear, that you want to ensure that your timer cannot re-enter your callback whilst you are processing a callback, and you want to do this without locking. You can achieve this using a System.Timers.Timer and ensuring that the AutoReset property is set to false. This will ensure that you have to trigger the timer on each interval manually, thus preventing any reentrancy:

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();
        }
    }
} 
贪恋 2024-12-06 18:18:30

作为对 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 ,但是如果您愿意,这很容易修复:将以上内容放入 NonReentrantTimer 类中它包装了 BCL 的 Timer 类。然后,您可以将 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();
    }
}

Complementing Tim Lloyd's solution for System.Timers.Timer, here's a solution to prevent reentrancy for cases where you want to use System.Threading.Timer instead.

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);

I believe you don't want interval to be accessed inside of the callback, but that is be easy to fix, if you want to: Put the above into a NonReentrantTimer class that wraps the BCL's Timer class. You would then pass the doSomeWork callback in as a parameter. An example of such a class:

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();
    }
}
不爱素颜 2024-12-06 18:18:30

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

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

重新编辑1:

您的选择是System.Timers.Timer和System.Threading.Timer,两者都需要防止重新进入。请参阅此页面并查找处理计时器事件重入部分。

I know I can implement locks inside my callback function, but I think it will be more elegant if it will be in the timer level

If locking is necessary then how could a timer arrange that? You're looking for a magical freebie.

Re Edit1:

Your choices are System.Timers.Timer and System.Threading.Timer, both need precautions against re-entrance. See this page and look for the Dealing with Timer Event Reentrance section.

跨年 2024-12-06 18:18:30
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();
    }

}
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();
    }

}
带上头具痛哭 2024-12-06 18:18:30

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

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

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

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

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

How timer could know about your shared data?

Timer callback is executed on some ThreadPool thread. So you will have at least 2 threads:

  1. Your main thread where timer is created and launched;
  2. Thread from ThreadPool for launching callback.

And it is your responsibility to provide correct work with your shared data.

Re edits: chibacity provided the perfect example.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文