Pregunta

I am using a LargeIntervalTimer from OpenNetCF to do polling when on a particular screen in certain conditions.

As I don't want the timer firing constantly I try to dispose of it once it's no longer needed, however calling Dispose() or setting Enabled to false in the UI thread will often hang the application.

I have tried moving the timer disposal code onto the timer's thread (by calling it in the tick method) and this just makes the hang more consistent, (albeit in a separate thread so the rest of the app keeps working).

Has anyone seen similar issues with this timer, and if so how did you fix them?

Code for start up:

_timer = new LargeIntervalTimer();
_timer.OneShot = false;
_timer.Tick += TimerTick;
_timer.Interval = new TimeSpan(0, 0, 30);
_timer.FirstEventTime = DateTime.Now.AddSeconds(30);
_timer.Enabled = true;

Code for shutdown:

if (_timer != null)
{
    _timer.Enabled = false;
    _timer.Dispose();
    _timer = null;
}
¿Fue útil?

Solución

Looking at the LIT source, I'm not seeing any reason why Disposing it would cause any issue. Dispose looks like this:

public void Dispose()
{
    lock (m_interlock)
    {
        m_disposing = true;

        if (Enabled)
        {
            Enabled = false;
        }

        if (m_quitHandle != null)
        {
            m_quitHandle.Set();
            m_quitHandle.Close();
            m_quitHandle = null;
        }
    }
}

As you can see, it sets Enabled to false and then sets a WaitHandle.

The Enabled implementation is similarly simple:

public bool Enabled
{
    get { return m_enabled; }
    set
    {
        lock (m_interlock)
        {
            m_cachedEnabled = value;

            if ((m_enabled && value) || (!m_enabled && !value))
            {
                return;
            }

            m_enabled = value;

            // force any existing waiting threads to exit
            if(ThreadCount > 0)
            {
                m_quitHandle.Set();
                Thread.Sleep(1);
            }

            if (m_enabled)
            {
                // start the wait thread
                ThreadPool.QueueUserWorkItem(InternalThreadProc);
            }
        }
    }
}

Basically if we're disabling and we were enabled, the same WaitHandle that Dispose will be setting is getting set. Redundant, yes, but not a problem. SetEvent is not a clocking call, so this code is all that happens as a direct result of your call and should be the only thing that would affect a "hang" scenario. But for completeness, let's look at what this event is triggering. Here's teh worker thread proc (which is largely the meat of the LIT class):

private void InternalThreadProc(object state)
{
    ThreadCount++;

    int source;
    string eventName = Guid.NewGuid().ToString();
    EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);

    try
    {
        while (m_enabled)
        {
            if (m_disposing) return;

            if (m_useFirstTime)
            {
                Notify.RunAppAtTime(string.Format(@"\\.\Notifications\NamedEvents\{0}", 
                    eventName), m_firstTime);
                m_useFirstTime = false;
            }
            else
            {
                // set up the next event
                Notify.RunAppAtTime(string.Format(@"\\.\Notifications\NamedEvents\{0}", 
                    eventName), DateTime.Now.Add(m_interval));
                m_firstTime = DateTime.MinValue;
            }

            if (m_disposing) return;
            source = OpenNETCF.Threading.EventWaitHandle.WaitAny(new WaitHandle[] 
                     { waitHandle, m_quitHandle });

            // see if it's the event
            if (source == 0)
            {
                m_cachedEnabled = null;

                // fire the event if we have a listener
                if (Tick != null)
                {
                    // we need to decouple this call from the current thread 
                    // or the lock will do nothing
                    ThreadPool.QueueUserWorkItem(new WaitCallback(
                        delegate
                        {
                            Tick(this, null);
                        }));
                }

                if (OneShot)
                {
                    if (m_cachedEnabled != null)
                    {
                        m_enabled = (m_cachedEnabled == true);
                    }
                    else
                    {
                        m_enabled = false;
                    }
                }
            }
            else
            {
                m_enabled = false;
            }
        }
    }
    finally
    {
        waitHandle.Close();
        ThreadCount--;
        if (ThreadCount == 0)
        {
            m_quitHandle.Reset();
        }
    }
}

About half way in you'll see a source = WaitAny call, which is where that event is caught. It simply returns 1 when the event from the above snipptes is called, which drops us to the else which sets m_enabled to false, which then exits the while loop and runs the finally block. The finally block resets the waithandle is all threads have exited and you're done. Again, pretty simple, and I see no potentials for a hang.

At this point all I can recommend is putting Debug.Writeline calls into the LIT source to see what's happening in your use case. It might shed a little more light on what's happening in your environment to cause bad behavior.

Bear in mind that Disposing the LIT still may leave a single notification active in your OS notifications queue, so the event registered is still going to fire one more time. That still should have zero impact, as we're no longer listening for it so it's just going to fire with no listeners, which is not a problem.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top