过度的CPU使用率

3

我正在处理一个问题。实际上,我需要在我的程序中使用不同的计时器,而 .Net Framework 的计时器并不能满足我的需求。因此,我决定创建自己的计时器,但是我的计时器却使用了太多的 CPU。这是我的代码:

using System;
using System.Threading;

namespace XXXXXXX.Common.Types
{
public delegate void TimerFinishedEventHandler(object sender, EventArgs e);

class Timer
{
    #region Attributes
    private long m_InitialTickCount;
    private long m_Interval;
    private Thread m_Thread;
    private bool m_Enabled;
    #endregion

    #region Events
    public event TimerFinishedEventHandler Finished;
    #endregion

    #region Constructors
    public Timer(long interval, TimerFinishedEventHandler e)
    {
        Finished += e;
        m_Interval = interval;
        Start(m_Interval);
    }
    #endregion

    #region Public methods
    /// <summary>
    /// Start the timer thread.
    /// </summary>
    public void Start(long interval)
    {
        m_Interval = interval;
        m_Enabled = true;
        m_InitialTickCount = Environment.TickCount;

        if (m_Thread == null)
        {
            m_Thread = new Thread(Check);
            m_Thread.Start();
        }
    }

    /// <summary>
    /// Stop the Timer.
    /// </summary>
    public void Stop()
    {
        m_Enabled = false;
    }

    /// <summary>
    /// Restart the Timer.
    /// </summary>
    public void Restart()
    {
        m_InitialTickCount = Environment.TickCount;
    }
    #endregion

    #region Private methods
    /// <summary>
    /// Check if the timer is finished or not.
    /// </summary>
    private void Check()
    {
        while (true)
        {
            if (!m_Enabled)
                return;

            if (Environment.TickCount > m_InitialTickCount + m_Interval)
            {
                OnFinished(EventArgs.Empty);
                return;
            }
        }
    }

    /// <summary>
    /// Called when the Timer is Finished.
    /// </summary>
    /// <param name="e">Event</param>
    protected virtual void OnFinished(EventArgs e)
    {
        if (Finished != null)
            Finished(this, e);
    }
    #endregion
}
}

有没有人有解决方案?因为当我启动程序时,会创建2或3个计时器,另一个线程运行并且我的CPU使用率达到100%。


1
你的代码不见了吗? - AMADANON Inc.
2
在你决定要编写自己的计时器之前,为什么不告诉我们你想做什么呢?很可能提供的计时器已经能够满足你的需求了。 - Jim Mischel
2
我认为这对你很有用。http://msdn.microsoft.com/zh-cn/magazine/cc164015.aspx - Hamlet Hakobyan
3
你认为为什么 System.Threading.Timers 会阻塞其他线程? - Guffa
1
如果您无法使多个计时器正常工作,那并不是计时器本身的限制。任何类型的计时器都支持多个计时器,它们之间的区别只在于它们被设计为更加高效/方便地用于特定的用途。例如,System.Windows.Forms.Timer 对于窗体 UI 使用非常方便,因为它使用消息触发计时器事件,而不是在单独的线程中启动方法。 - Guffa
显示剩余7条评论
3个回答

4
没有任何理由不允许你拥有多个计时器。我有一些程序有数百个计时器,而在任何时候,其中只有几个会实际执行工作。计时器的目的是允许您安排定期操作,并且在处理这些操作时不消耗任何CPU资源。也就是说,如果您将计时器设置为每分钟运行一次,则该计时器不占用线程,不消耗任何内存(除了计时器句柄和回调地址的代币数量),也不消耗任何CPU资源。只有当计时器每分钟“滴答”一次时,才会分配一个线程来执行相应的代码。通常这是一个已经存在的线程池线程,因此线程启动时间可以忽略不计。
使用计时器非常容易:您创建一个方法让计时器执行,并安排计时器运行它。例如:
System.Threading.Timer myTimer = 
    new System.Threading.Timer(MyTimerProc, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));

void MyTimerProc(object state)
{
    // do something here
}

您可以再设置一个定时器,每30秒触发一次,并执行不同的定时过程:
System.Threading.Timer myOtherTimer = 
    new System.Threading.Timer(MyOtherTimerProc, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));

void MyOtherTimerProc(object state)
{
    // do something else here
}

计时器不会互相干扰。当然,如果计时器进程中的代码修改了共享数据(例如,两个进程都试图更新列表或字典),那么你必须同步访问该共享数据或使用并发数据结构。
如果处理计时器进程的处理时间超过计时器周期,则可能会出现可重入性问题。如果 MyTimerProc 中的处理时间超过 60 秒,则另一个计时器滴答声会出现,现在你有两个线程执行该计时器进程。如果你的代码没有设置好,这可能会导致许多不同类型的问题。通常,通过将计时器设置为一次性,并在每个处理周期结束时重新启动它来消除该问题。这里在 Stack Overflow 上有做法的示例。

System.Timers.TimerSystem.Threading.Timer的组件包装器。认为它“针对高性能线程优化”之类的想法是愚蠢的。 System.Timers.Timer提供了一个熟悉的面向事件的接口,并且还提供了一个SynchronizingObject,它允许您在特定线程上引发事件,而不必像使用System.Threading.Timer那样明确地调用Invoke。通常,这只在UI应用程序中有用。

System.Timers.Timer有一个特别丑陋的“功能”,我认为它是一个错误:它压制异常。如文档所述:

在 .NET Framework 版本 2.0 及更早版本中,计时器组件捕获并抑制由 Elapsed 事件的事件处理程序引发的所有异常。

这种行为仍然存在于.NET 4.5中。问题在于,如果您的Elapsed事件:

private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
    // do stuff here
}

如果您的事件处理程序抛出异常,它将传播回计时器代码,该代码会压制异常并永远不会告诉您。实际上,计时器会执行以下操作:
try
{
    OnTimedEvent(source, args);
}
catch
{
    // Squash those pesky exceptions. Who needs them?
}

这是一个错误隐藏器,因为您永远不知道异常何时被抛出。所以您的程序无法正常工作,也无法找出原因。因此,我强烈建议您不要使用System.Timers.Timer。相反,请使用System.Threading.Timer,毕竟它是System.Timers.Timer构建的基础。

谢谢。我将开始尝试使用System.Threading.Timers,并向您展示我的代码。 - Veriditas

2
直接回答您的问题,您的CPU使用率很高的原因是您使用了一个紧密的while循环来检查已经过去的事件。这有时被称为“自旋锁”(因为它是一种非常低效的实现信号量的方式之一,其中线程在一个紧密的循环中检查锁定变量,因此它“自旋”)。
与其使用紧密的循环,您需要阻塞并允许其他东西运行一段时间:
private void Check()
{
    while (true)
    {
        if (!m_Enabled)
            return;

        Thread.Sleep(10); //10 millisecond resolution for this timer
        if (Environment.TickCount > m_InitialTickCount + m_Interval)
        {
            OnFinished(EventArgs.Empty);
            return;
        }
    }
}

分辨率取决于您的睡眠时间。话虽如此,提供的计时器应始终足够,即使使用2个System.Threading.Timers也可以。我个人使用多个System.Threading.Timer和System.Timers.Timer而没有遇到问题。

当然,对于所有这些计时器,您需要小心访问共享资源(也许这就是您所说的现有计时器阻止其他线程的意思?)。死锁是多线程中非常真实的情况,但与计时器无关。


1
谢谢您提供如此详细的答案。我已经尝试了Thread.Sleep,它非常完美。事实上,我只需要分钟级别的精度,而不是秒级别的。我添加了Thread.Sleep(50000); 现在非常完美! - Veriditas
很高兴我能帮到你! - BradleyDotNET
1
啊!Sleep()轮询循环。不要啊! - Martin James
@MartinJames,我从未说过这是一个好主意,事实上,我试图明确实际计时器应该被使用。为了再次明确,我不建议以这种方式使用Thread.Sleep。 - BradleyDotNET
是的,就像我之前说的那样,明天我会学习使用 System.Timers.Time 和 System.Threading.Timers。 - Veriditas

0

致@Jim Mischel先生:

在我的课程中,连接到我的网站,检查数据:

        #region Attributes
        private static Timer m_TimerNextCheck;
        #endregion

        #region Méthodes publiques
        public static void StartCheck()
        {
            Thread licenceThread = new Thread(Checking);
            licenceThread.Start();
        }
        #endregion

        #region Méthodes privées
        private static void Checking()
        {
            //connect to the website

            try
            {
                HttpWebResponse httpWebResponse = (HttpWebResponse) request.GetResponse();

                StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream(), Encoding.Default);

                string response = streamReader.ReadToEnd();

                httpWebResponse.Close();

                if (//Some code)
                {
                    //Some code
                }
                else
                {

                    if (m_TimerNextCheck == null)
                        m_TimerNextCheck = new Timer(TimerNextCheckFinished, null, 300000, Timeout.Infinite);
                    else
                        m_TimerNextCheck.Change(300000, Timeout.Infinite);

                }
            }
            catch (WebException exception)
            {
                //Some code

                if (m_TimerNextCheck == null)
                    m_TimerNextCheck = new Timer(TimerNextCheckFinished, null, 60000, Timeout.Infinite);
                else
                    m_TimerNextCheck.Change(60000, Timeout.Infinite);
            }
        }

        private static void TimerNextCheckFinished(object statusInfos)
        {
            Checking();
        }
        #endregion

在另一个类中:
    #region Attributs
    private Thread m_ConnectionThread;
    private Timer m_TimerConnectionThread;
    #endregion

    #region Méthodes publiques
    public void Launch()
    {
        m_ConnectionThread = new Thread(Connect);
        m_ConnectionThread.Start();
    }

    public void GetNextMeal()
    {
        //Some code

        if (//Some code)
        {
            //Some code

            if (m_TimerConnectionThread == null)
                m_TimerConnectionThread = new Timer(TimerConnectionThreadFinished, null, 
                    (int)TimeSpan.FromHours(difference.Hour).TotalMilliseconds + 
                    (int)TimeSpan.FromMinutes(difference.Minute).TotalMilliseconds, Timeout.Infinite);
            else
                m_TimerConnectionThread.Change((int)TimeSpan.FromHours(difference.Hour).TotalMilliseconds + 
                    (int)TimeSpan.FromMinutes(difference.Minute).TotalMilliseconds, Timeout.Infinite);
        }
        else
        {
            //Some code
        }
    }

    public void TryReconnect(int minute)
    {
        //Some code

        if (m_TimerConnectionThread == null)
            m_TimerConnectionThread = new Timer(TimerConnectionThreadFinished, null, (int)TimeSpan.FromMinutes(minute).TotalMilliseconds,
                Timeout.Infinite);
        else
            m_TimerConnectionThread.Change((int)TimeSpan.FromMinutes(minute).TotalMilliseconds, Timeout.Infinite);

        //Some code
    }

    //Some code
    #endregion

    #region Méthodes privées
    private void Connect()
    {
        if (m_TimerConnectionThread != null)
            m_TimerConnectionThread.Change(Timeout.Infinite, Timeout.Infinite);

        //Some code
    }

    //Some code

    private void TimerConnectionThreadFinished(object stateInfo)
    {
        Connect();
    }
    #endregion

它运行良好!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接