简体   繁体   English

c#System.Threading.Timer等待处理

[英]c# System.Threading.Timer wait for dispose

I have a class which uses a Timer. 我有一个使用Timer的类。 This class implements IDispose . 这个类实现了IDispose I would like to wait in the Dispose method until the timer will not fire again. 我想在Dispose方法中等待,直到计时器不会再次触发。

I implement it like this: 我这样实现它:

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

Is this implementation correct? 这个实现是否正确? I think it mainly depends on the question if _ timerIsExecuting == 0 is an atomic operation. 我认为这主要取决于_ timerIsExecuting == 0是否为原子操作的问题。 Or would I have to use a WaitHandle . 或者我是否必须使用WaitHandle For me it seems it would make the code unnecessarily complicated... 对我来说,它似乎会使代码变得不必要地复杂化......

I am not an expert in multi-threading, so would be happy about any advice. 我不是多线程专家,所以对任何建议都会感到高兴。

Unless you have a reason not to use System.Threading.Timer This has a Dispose method with a wait handle 除非你有理由不使用System.Threading.Timer这有一个带有等待句柄的Dispose方法

And you can do something like, 你可以做点什么,

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

Generally one can use the Timer.Dispose(WaitHandle) method, but there's a few pitfalls: 通常可以使用Timer.Dispose(WaitHandle)方法,但是有一些缺陷:

Pitfalls 陷阱

  • Support for multiple-disposal (see here ) 支持多处理(见这里

If an object's Dispose method is called more than once, the object must ignore all calls after the first one. 如果多次调用对象的Dispose方法,则该对象必须忽略第一个之后的所有调用。 The object must not throw an exception if its Dispose method is called multiple times. 如果多次调用Dispose方法,则该对象不得抛出异常。 Instance methods other than Dispose can throw an ObjectDisposedException when resources are already disposed. 除了Dispose之外的实例方法可以在已经处置资源时抛出ObjectDisposedException。

  • Timer.Dispose(WaitHandle) can return false. Timer.Dispose(WaitHandle)可以返回false。 It does so in case it's already been disposed (i had to look at the source code). 它是这样做的,以防它已被处理(我必须查看源代码)。 In that case it won't set the WaitHandle - so don't wait on it! 在这种情况下,它不会设置WaitHandle - 所以不要等待它! (Note: multiple disposal should be supported) (注意:应支持多次处理)

  • not handling a WaitHandle timeout. 没有处理WaitHandle超时。 Seriously - what are you waiting for in case you're not interested in a timeout? 说真的 - 如果你对超时不感兴趣,还等什么呢?
  • Concurrency issue as mentioned here on msdn where an ObjectDisposedException can occur during (not after) disposal. 在msdn中提到的并发问题,其中处理期间 (而不是之后)可能发生ObjectDisposedException
  • Timer.Dispose(WaitHandle) does not work properly with - Slim waithandles, or not as one would expect. Timer.Dispose(WaitHandle)无法正常使用 - Slim waithandles,或者不像人们期望的那样。 For example, the following does not work (it blocks forever): 例如,下列工作(它永远块):
 using(var manualResetEventSlim = new ManualResetEventSlim)
 {
     timer.Dispose(manualResetEventSlim.WaitHandle);
     manualResetEventSlim.Wait();
 }

Solution

Well the title is a bit "bold" i guess, but below is my attempt to deal with the issue - a wrapper which handles double-disposal, timeouts, and ObjectDisposedException . 好吧标题有点“大胆”我想,但下面是我尝试处理这个问题 - 一个处理双重处理,超时和ObjectDisposedException的包装器。 It does not provide all of the methods on Timer though - but feel free to add them. 虽然它没有提供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;
            }
        }
    }
}

Why you need to dispose the Timer manually? 为什么需要手动配置定时器? Isn't there any other solution. 没有任何其他解决方案。 As a rule of thumb, you're better leaving this job to GAC. 根据经验,您最好将此工作留给GAC。 – LMB 56 mins ago - LMB 56分钟前

I am developing an ASP.NET application. 我正在开发一个ASP.NET应用程序。 The timer is disposed on the call of Dispose of the HttpApplication. 计时器在HttpApplication的Dispose调用时处理。 The reason: A callback could access the logging system. 原因是:回调可以访问日志记录系统。 So i have to assure the before disposing the logging system the timer is disposed. 所以我必须保证在配置计时器之前处理记录系统。 – SACO 50 mins ago - SACO 50分钟前

It looks like you have a Producer/Consumer pattern, using the timer as Porducer. 看起来你有一个Producer / Consumer模式,使用定时器作为Porducer。

What I'd do in this case, would be to create a ConcurrentQueue() and make the timer enqueue jobs to the queue. 在这种情况下我要做的是创建一个ConcurrentQueue()并使计时器将作业排入队列。 And then, use another safe thread to read and execute the jobs. 然后,使用另一个安全线程来读取和执行作业。

This would prevent a job from overlapping another, which seems to be a requirement in your code, and also solve the timer disposing problem, since you could yourQueue == null before adding jobs. 这可以防止作业与另一个作​​业重叠,这似乎是代码中的一项要求,并且还解决了计时器处理问题,因为在添加作业之前你可以使用yourQueue == null

This is the best design. 这是最好的设计。

Another simple, but not robust, solution, is running the callbacks in a try block. 另一个简单但不健壮的解决方案是在try块中运行回调。 I don't recommend to dispose the Timer manually. 我不建议手动配置定时器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM