【问题标题】:c# System.Threading.Timer wait for disposec# System.Threading.Timer 等待处理
【发布时间】:2012-11-15 11:18:28
【问题描述】:

我有一个使用定时器的类。此类实现IDispose。我想在Dispose 方法中等待,直到计时器不再触发。

我是这样实现的:

private void TimerElapsed(object state)
{
    // do not execute the callback if one callback is still executing
    if (Interlocked.Exchange(ref _timerIsExecuting, 1) == 1) 
        return;

    try
    {
        _callback();
    }
    finally
    {
        Interlocked.Exchange(ref _timerIsExecuting, 0);
    }
}

public void Dispose()
{
    if (Interlocked.Exchange(ref _isDisposing, 1) == 1)
        return;

    _timer.Dispose();

    // wait until the callback is not executing anymore, if it was
    while (_timerIsExecuting == 0) 
    { }

    _callback = null;
}

这个实现是否正确?我认为这主要取决于 _timerIsExecuting == 0 是否是原子操作的问题。或者我必须使用WaitHandle。对我来说,这似乎会使代码变得不必要地复杂......

我不是多线程方面的专家,所以我很乐意听取任何建议。

【问题讨论】:

  • 我正在开发一个 ASP.NET 应用程序。计时器在 HttpApplication 的 Dispose 调用上被释放。原因:回调可以访问日志系统。所以我必须确保在处理日志系统之前处理计时器。
  • @LMB 当然,如果一个方法有一个IDisposable,你就丢弃它。是的,GC 可以清理,但如果这足够有效,为什么还要使用IDisposable
  • @MAfifi 当你使用非托管资源时。

标签: c# multithreading timer dispose


【解决方案1】:

除非你有理由不使用System.Threading.Timer 这有一个带有等待句柄的Dispose 方法

你可以做类似的事情,

private readonly Timer Timer;
private readonly ManualResetEvent TimerDisposed;
public Constructor()
{
    Timer = ....;
    TimerDisposed = new ManualResetEvent(false);
}

public void Dispose()
{
    Timer.Dispose(TimerDisposed);
    TimerDisposed.WaitOne();
    TimerDisposed.Dispose();
}

【讨论】:

  • 这段代码有两个问题:不支持多次处理(参见here),另外还有一个documented 竞争条件,在处理过程中可能会出现ObjectDisposedException - 这应该被提及。
【解决方案2】:

一般可以使用Timer.Dispose(WaitHandle) 方法,但有一些陷阱:

陷阱

  • 支持多次处理(请参阅here

如果多次调用对象的 Dispose 方法,则对象必须忽略第一次调用之后的所有调用。如果多次调用其 Dispose 方法,则该对象不得引发异常。当资源已被释放时,除 Dispose 之外的实例方法可能会引发 ObjectDisposedException。

  • Timer.Dispose(WaitHandle) 可以返回 false。如果它已经被处理(我必须查看源代码),它会这样做。在这种情况下,它不会设置WaitHandle - 所以不要等待它! (注:应支持多次处理)

  • 未处理WaitHandle 超时。说真的 - 如果您对超时不感兴趣,您还在等什么?
  • here on msdn 提到的并发问题,其中ObjectDisposedException 可能发生在处理期间(而不是处理之后)。
  • Timer.Dispose(WaitHandle) 不能与 -Slim 等待句柄一起正常工作,或者与预期不同。例如,以下操作起作用(它永远阻塞):
 using(var manualResetEventSlim = new ManualResetEventSlim)
 {
     timer.Dispose(manualResetEventSlim.WaitHandle);
     manualResetEventSlim.Wait();
 }

解决方案

我猜这个标题有点“粗体”,但下面是我处理这个问题的尝试——一个处理双重处理、超时和ObjectDisposedException的包装器。虽然它没有提供Timer 上的所有方法 - 但请随意添加它们。

internal class Timer
{
    private readonly TimeSpan _disposalTimeout;

    private readonly System.Threading.Timer _timer;

    private bool _disposeEnded;

    public Timer(TimeSpan disposalTimeout)
    {
        _disposalTimeout = disposalTimeout;
        _timer = new System.Threading.Timer(HandleTimerElapsed);
    }

    public event Signal Elapsed;

    public void TriggerOnceIn(TimeSpan time)
    {
        try
        {
            _timer.Change(time, Timeout.InfiniteTimeSpan);
        }
        catch (ObjectDisposedException)
        {
            // race condition with Dispose can cause trigger to be called when underlying
            // timer is being disposed - and a change will fail in this case.
            // see 
            // https://msdn.microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
            if (_disposeEnded)
            {
                // we still want to throw the exception in case someone really tries
                // to change the timer after disposal has finished
                // of course there's a slight race condition here where we might not
                // throw even though disposal is already done.
                // since the offending code would most likely already be "failing"
                // unreliably i personally can live with increasing the
                // "unreliable failure" time-window slightly
                throw;
            }
        }
    }

    private void HandleTimerElapsed(object state)
    {
        Elapsed.SafeInvoke();
    }

    public void Dispose()
    {
        using (var waitHandle = new ManualResetEvent(false))
        {
            // returns false on second dispose
            if (_timer.Dispose(waitHandle))
            {
                if (!waitHandle.WaitOne(_disposalTimeout))
                {
                    throw new TimeoutException(
                        "Timeout waiting for timer to stop. (...)");
                }
                _disposeEnded = true;
            }
        }
    }
}

【讨论】:

    【解决方案3】:

    为什么需要手动处理 Timer?有没有其他的 解决方案。根据经验,您最好将这份工作留给 GAC。 – LMB 56 分钟前

    我正在开发一个 ASP.NET 应用程序。计时器在 HttpApplication 的 Dispose 调用上被释放。原因:回调 可以访问日志系统。所以我必须保证之前 配置记录系统,配置定时器。 – SACO 50 分钟前

    看起来你有一个生产者/消费者模式,使用计时器作为生产者。

    在这种情况下,我要做的是创建一个 ConcurrentQueue() 并使计时器将作业排入队列。然后,使用另一个安全线程来读取和执行作业。

    这将防止一个作业与另一个作​​业重叠,这似乎是您的代码中的一个要求,并且还解决了计时器处置问题,因为您可以在添加作业之前yourQueue == null

    这是最好的设计。

    另一个简单但不可靠的解决方案是在 try 块中运行回调。我不建议手动处理 Timer。

    【讨论】:

    • 计时器之类的东西对于 GC 来说很难处理。问题是,如果例如计时器事件仅用于每秒对某个对象“George”执行某些操作,只要有人对 George 感兴趣,计时器就会有用,并且一旦潜在感兴趣的实体对 George 的所有引用都被放弃,计时器将不再有用。可以通过让计时器为 George 持有WeakReference 来处理这种情况,并在弱引用死亡时自行处理;这种方法可能会使用 GC,但几乎不会“将工作交给 GC”。
    猜你喜欢
    • 2012-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-04
    • 1970-01-01
    • 1970-01-01
    • 2021-11-01
    • 2020-12-18
    相关资源
    最近更新 更多