7

我有一个使用定时器的类。该类实现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. 对我来说,这似乎会使代码变得不必要地复杂......

我不是多线程方面的专家,所以对任何建议都会很高兴。

4

3 回答 3

10

除非你有理由不使用System.Threading.Timer This has a Disposemethod with a wait handle

你可以做类似的事情,

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();
}
于 2012-11-15T12:30:50.650 回答
6

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

陷阱

  • 支持多次处理(见这里

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

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

  • 不处理WaitHandle超时。说真的 - 如果你对超时不感兴趣,你还在等什么?
  • 在 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;
            }
        }
    }
}
于 2016-02-19T17:26:50.207 回答
-1

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

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

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

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

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

这是最好的设计。

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

于 2012-11-15T12:37:04.917 回答