如何处置 LargeIntervalTimer

发布于 2024-10-18 13:43:35 字数 664 浏览 4 评论 0原文

我正在使用 OpenNetCF 的 LargeIntervalTimer 在特定条件下在特定屏幕上进行轮询。

因为我不希望计时器不断触发,所以一旦不再需要它,我就会尝试将其丢弃,但是在 UI 线程中调用 Dispose() 或将 Enabled 设置为 false 通常会挂起应用程序。

我尝试将计时器处理代码移动到计时器的线程上(通过在 tick 方法中调用它),这只会使挂起更加一致(尽管在单独的线程中,以便应用程序的其余部分继续工作)。

有没有人见过这个计时器的类似问题,如果是的话,你是如何解决这些问题的?

启动代码:

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

关闭代码:

if (_timer != null)
{
    _timer.Enabled = false;
    _timer.Dispose();
    _timer = null;
}

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

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

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

发布评论

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

评论(1

怀念你的温柔 2024-10-25 13:43:35

查看 LIT 源代码,我没有看到任何处理它会导致任何问题的原因。 Dispose 如下所示:

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

如您所见,它将 Enabled 设置为 false,然后设置 WaitHandle

Enabled 实现同样简单:

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

基本上,如果我们禁用并且启用,则将设置 Dispose 将设置的相同 WaitHandle。冗余,是的,​​但不是问题。 SetEvent 不是时钟调用,因此此代码是调用的直接结果而发生的所有事情,并且应该是唯一会影响“挂起”场景的事情。但为了完整起见,让我们看看这个事件触发了什么。下面是工作线程过程(主要是 LIT 类的主要内容):

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

大约一半时,您将看到一个 source = WaitAny 调用,这是捕获该事件的地方。当调用上面片段中的事件时,它只是返回 1,这会将我们带到将 m_enabled 设置为 false 的 else,然后退出 while 循环并运行 finally堵塞。当所有线程都已退出并且完成时,finally 块会重置等待句柄。同样,非常简单,而且我认为没有任何悬念的潜力。

此时,我所能建议的就是将 Debug.Writeline 调用放入 LIT 源中,以查看您的用例中发生了什么。它可能会更多地揭示您的环境中发生的导致不良行为的原因。

请记住,处置 LIT 仍可能会使操作系统通知队列中的单个通知处于活动状态,因此注册的事件仍将再次触发。这仍然应该具有零影响,因为我们不再监听它,所以它只会在没有听众的情况下触发,这不是问题。

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.

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