【问题标题】:CPU is maxed when running a while loop inside a Long Running task在长时间运行的任务中运行 while 循环时 CPU 已达到最大值
【发布时间】:2022-01-19 02:17:15
【问题描述】:

我正在对消息传递服务器进行持续轮询。当消息到达服务器时,我抓取消息并处理它。不幸的是,这是一个简单的任务使用 80-100% 的 CPU。 更新:我已将其简化为 while 循环本身。 10 个任务中的 while 循环导致 CPU 以 100% 轻松达到最大值。

    int i = 0;
    while(true){
     //At the start of every minute
       if (DateTime.Now.Seconds == 0)
      {
        i++;
      }
    }

有没有一种方法可以限制循环,或者有更好更好的方法来编写此代码,使其不使用 100% 的 CPU?我曾尝试添加 1 秒的 Task.Delay,但这并没有多大帮助。

如果您能提供任何帮助或建议,我将不胜感激。

// First I poll 10 different locations for messages and each task polls its own queue
foreach(Queue queue in queueList)
{
task.Add(Task.Run(() => PollIndividualQueue(queue));
}

t= Task.WhenAll(task.ToArray()).WithAggregatedExceptions();
t.Wait();

//Then in the PollIndividualQueue method I implement the while loop that constantly polls a message queue for the next hour
private async Task<string> PollIndividualQueue(Queue queue)
{
    var cancellationToken = new CancellationTokenSource(Timespan.FromMinutes(60)).Token;

  while(!cancellationToken.IsCancellationRequested)
   {
       //Poll the queue and if there is a message grab it and process it
       if(!await GetMessage())
        {
           //I call a stored procedure that inserts this message into the database.
         using(var conn = new SqlConnection(...)
        {
           using(var cmd = new SqlCommand(MyStoredProc, conn)
               {
                cmd.CommandType = Command.StoredProcedure;
                  cmd.Parameters.Add(new SQlParameter(...InputMessage));
                await conn.OpenAsync();
                await cmd.ExecuteNonQueryAsync();
             }
            }
            }
        else
        {
          await Task.Delay(1000);
        }
   }

}

private Task<bool> getMessage(Queue queue)
  {
   object myLock = new object();

   
      try
      {
        Monitor.Enter(myLock);
        queue.Get(message);
      }
      catch(MQException ex)
      {
       if(ex.ReasonCode == 2033)
       {
         return false;
       }
      }
     finally
     {
       Monitor.Exit(myLock);
     }    
}

编辑:谢谢大家的 cmets,但它似乎偏离了实际问题。问题是有没有办法使用总 CPU 的百分比? 我可以从字面上将所有这些代码从 While 循环中取出,并且 CPU 仍处于 100%。 while 循环似乎是驱动 CPU 负载的原因。

【问题讨论】:

  • 你为什么要Task.Run?就做task.Add(PollIndividualQueue(queue)); 为什么GetMessage 不是异步的? if 中的任何内容是异步的还是只是 CPU 密集型代码?你真的应该使用await 而不是Wait()
  • 您的 getMessage 有问题,它使用了本地锁变量,并且没有相应的退出调用。
  • 锁定一个局部变量是没有意义的。你永远不会与另一个线程竞争。
  • 如果您不打算发布实际代码,或者至少是最低限度的复制,那么我们应该如何帮助您?如果你愿意,我可以做一些随机猜测,但它们不太可能有用。或者您可以发布 getMessage 方法的其余部分,这可能是问题所在。
  • 我建议您使用确实实现异步侦听器的 IBM MQ XMS.NET API。如果您想按照当前的方式执行此操作,则将接收器超时添加到您的 get 中,这将在返回 2033 之前等待消息到达队列的时间量,但一旦消息可用就会返回。

标签: c# loops task task-parallel-library ibm-mq


【解决方案1】:

好的。很明显,你是新手,所以我会尽量善待我的cmets。

  1. 您的问题顶部的 while 循环是非常糟糕的编程。您想知道为什么它会耗尽您的 CPU 吗?如果您在 Intel i9-9900K 上运行,那么该循环将每 执行数十亿次。我不是在开玩笑!!

  2. 如果你想每秒做一次,那么你需要做一个简单的时间计算然后睡觉。为了让 CPU 安静下来,你的代码应该是:

    int i = 0;
    while (true) {
       //At the start of every minute
       Thread.Sleep((int)(60000 - ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) % 60000)));
       i++;
    }
    
  3. 我真的不明白你在 getMessage 方法中使用了锁。 Queue 的 Get 方法在尝试检索消息时会阻塞。

  4. 如果您要使用线程,那么为什么不让每个线程单独连接到队列管理器,因为没有理由在线程之间共享连接。

【讨论】:

  • 不,它不会每秒运行 530 亿次,但仍然在每秒数十亿次的范围内,并消耗 100% 的 CPU 内核。这是检查时间的最糟糕的方法。正确的解决方案是使用timer,这导致比睡眠更正确的时间
  • Intel 说 i9-9900K CPU 每秒会执行 534.29 亿条指令,所以我将与您分摊差价。 :)
  • 你从哪里得到这个号码的?不,那个循环不是一条指令,而是至少有一些方法调用来获取日期时间值、比较和跳转。请记住,系统调用非常慢,当编组到系统调用等本机函数时,C# 调用也很慢,甚至更慢。所以我希望您需要将数字除以至少 10 才能获得循环在一秒钟内运行的次数
  • 这个答案有很多错误。线程切换是一项非常昂贵的操作,这意味着Thread.Sleep 会损害性能。如果在任务、MVC 操作或其他任何使用线程轮询线程的内容中调用它,Sleep 将不再使用它。这就是使用计时器或await Task.Delay() 的原因。
  • 对于共享队列连接,不能仅仅假设一个连接有多昂贵或者它是否是线程安全的。例如,HttpClient 是线程安全的,并且可以重复使用,因为 TCP 级别的连接 非常昂贵。数据库连接不是线程安全的,它们很昂贵,但连接池使得重新打开它们变得很便宜。
猜你喜欢
  • 1970-01-01
  • 2013-07-08
  • 2014-09-10
  • 1970-01-01
  • 2012-08-13
  • 2022-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-14
相关资源
最近更新 更多