即使ThreadPool.GetAvailableThreads()指示数量很多,Tasks是否也可能会遇到调度缓慢的问题?

时间:2019-04-03 12:26:32

标签: c# .net task-parallel-library

我们有一个正在生产中运行的服务器,该服务器接收数据并放入queueA中。另一个线程一次从queueA获取一个项目,对其进行处理并将其放入queueB

此设置实际上是镜像的,因此在主动对主动的冗余设置中,两个镜像服务器都接收完全相同的数据并以相同的方式处理它们。

大约每年一次,其中一台服务器的邮件会大量堆积在queueA中,而另一台服务器可以很好地处理相同的数据。

因此,问题似乎出在queueA中,并进行处理并排队到queueB中。除了(完全没有必要)使用Task库(如以下精简版所示)之外,那里没有什么其他事情了。

public IAsyncResult BeginReceive()
{
  Task<object> task = new Task<object>(_ =>
  {
    object message;
    if (!queueA.TryDequeue(out message))
    {
      if (queueA.IsEmpty)
        waitQueueA.WaitOne(10); // waitQueueA is properly signaled whenever an item is put into queueA
    }
    return message;
  }, null, TaskCreationOptions.PreferFairness);

  task.ContinueWith((t) =>
  {
    object receivedMessage = t.Result;
    if (receivedMessage != null)
    {
      lock (bLock) // bLock is only used by this piece of code
      {
        queueB.Enqueue(receivedMessage);
      }
    }
    else
    {
      Thread.Sleep(1);
    }
    BeginReceive(OnReceive, channel);
  });

  task.Start();
  return task;
}

public object EndReceive(IAsyncResult result)
{
  Task<object> task = (Task<object>) result;
  return task.Result;
}

忽略代码的许多特质(个人而言,我会为此创建一个专用线程,并在一个大的while (true) { }循环中执行以上所有操作,而无需涉及任何Task),在什么情况下可能会使它的性能如此差,以至于循环以每秒15次迭代的速度旋转,而在少于50次迭代/秒的情况下达到最大值?我们每5秒记录一次ThreadPool.GetAvailableThreads(),它表示整个过程中有数千个可用线程。

该代码在大多数时间都能正常运行(但足够),但是当它失败时,它似乎从程序的开始一直到它的结束都失败了,这在一个小时左右的范围内(当内存耗尽时) queueA及其项目。因此,该程序似乎可以进入某种时髦的状态,无法恢复。

下面是每秒处理的项目数量的图表,对于好机器和坏机器,水平轴是时间轴。 (请注意,垂直轴是对数的)

enter image description here

图表显示例如多数情况下,“坏”服务器的上限大约为17/18个项目/秒,而“好”服务器能够处理3700个项目/秒,这取决于接收和输入新项目的速率queueA

由于我不太熟悉任务库的复杂性,我想知道PreferFairnessBeginReceive的异步(因此不是真的)“递归”调用的组合是否可以在某些随机情况下导致此问题。还有其他想法如何深入浅出吗?

我删除了几个try {} catch { error.log(); }构造来简化它。没有记录任何错误,因此我相信代码不会引发异常。并且queueA不会单调增长,有时会缩小一点,因此该循环似乎很活跃,尽管很慢。

1 个答案:

答案 0 :(得分:0)

Here是关于该主题的很好的读物。

  

将任务调度到默认调度程序时,调度程序将查找   查看是否正在从中排队任务的当前线程   是具有自己的本地队列的ThreadPool线程。如果不是,则   工作项将排队到全局队列中。如果是,则调度程序   还将检查Task的TaskCreationOptions值   包括PreferFairness标志,默认情况下不启用。如果   即使线程确实有自己的本地队列,也会设置标志   调度程序仍会将任务排队到全局队列中,而不是   到本地队列。通过这种方式,该任务将被考虑   公平地与所有其他全局排队的工作项一起。