【问题标题】:Task stays in WaitingToRun state for abnormally long time任务异常长时间处于 WaitingToRun 状态
【发布时间】:2024-01-22 18:06:01
【问题描述】:

我有一个程序可以处理并行运行的各种任务。单个任务充当各种管理器,确保在运行下一个任务之前满足某些条件。但是,我发现有时某个任务会长时间处于 WaitingToRun 状态。下面是代码:

mIsDisposed = false;
mTasks      = new BlockingCollection<TaskWrapper>(new ConcurrentQueue<TaskWrapper>());

Task.Factory.StartNew(() => {
    while (!mIsDisposed) {
         var tTask = mTasks.Take();
         tTask.task.Start();
         while (tTask.task.Status == TaskStatus.WaitingToRun) {
             Console.WriteLine("Waiting to run... {0}", tTask.task.Id);
             Thread.Sleep(200);
         }
         tTask.ready.Wait();
     }
     mTasks.Dispose();
});

DoWork();
DoWork();
DoWork();
DoWork();
DoWorkAsync();
DoWorkAsync();
DoWorkAsync();
DoWorkAsync();
DoWorkAsync();
DoWork();

TaskWrapper 的定义非常简单:

private class TaskWrapper
{
    public Task task  { get; set; }
    public Task ready { get; set; }
}

目前仅在 2 个地方添加任务:

public void DoWork()
{
    DoWorkAsync().Wait();
}

public Task DoWorkAsync()
{
    ManualResetEvent next = new ManualResetEvent(false);

    Task task  = new Task(() => ActualWork(next));
    Task ready = Task.Factory.StartNew(() => next.Wait());

    mTasks.Add(new TaskWrapper() {
        task  = task,
        ready = ready
    });
    return task;
}

ActualWork(next) 调用 next.Set()

这会将工作排入队列并等待直到设置了next,然后才允许下一个工作项继续进行。您可以通过调用DoWork() 等待整个任务完成后再继续,或者一次将多个任务排队(应该在设置next 之后运行)。

但是,当通过DoWorkAsync() 添加任务时,在调用tTask.task.Start() 之后,tTask.task 处于 WaitingToRun 状态很长一段时间(如 30 秒到一分钟),然后神奇地开始运行。我使用 while 循环对此进行了监控,Waiting To Run... # 将显示相当长的一段时间。

调用DoWork() 总是立即运行。我确定这与在设置为运行的任务上调用 Wait 有关。

在这里,我不知所措。

更新:

我已经设法使代码正常工作,但我仍然想知道为什么会出现问题。

经过一些实验性更改后,我设法解决了自己的问题,但这更像是“哦,所以我不能那样做”,而不是一个好的解决方案。事实证明,我的问题是让任务排队运行太快。通过将DoWorkAsync() 修改为不再使用Task.Factory.StartNew 并将tTask.ready.Wait() 更改为tTask.ready.RunSynchronously,我已经设法解决了我的问题。

TaskScheduler 延迟我的任务安排是否有原因?我是否饱和了一些基础资源?这是怎么回事?

【问题讨论】:

    标签: c# concurrency task


    【解决方案1】:

    线程将在系统的线程池中运行。线程池始终具有最小数量的可用线程(请参阅ThreadPool.SetMinThreads())。如果您尝试创建多于该数量的线程,则会在每个新线程启动之间引入大约 500 毫秒的延迟。

    线程池中还有一个最大线程数(请参阅ThreadPool.GetMaxThreads()),如果达到该限制,将不会创建新线程;它会等到一个旧线程死亡后再安排一个新线程(或者更确切地说,重新安排旧线程来运行你的新线程,当然)。

    不过,您不太可能达到该限制 - 它可能超过 1000。

    【讨论】:

    • 就是这样!奇怪的是,他们延迟了新线程的启动。我认为这是为了让您的程序不会无意中尝试扼杀系统上的其他进程......但是我不知道。
    【解决方案2】:

    刚刚遇到类似的问题。

    我有一堆类似的任务正在运行无限循环,其中一个任务有时会永久保持在 WaitingToRun 状态。

    以这种方式创建任务对我有用:

    _task = new Task(() => DoSmth(_cancellationTokenSource.Token), TaskCreationOptions.LongRunning);
    _task.Start();
    

    【讨论】:

      【解决方案3】:

      好的,我刚刚遇到了类似的问题。运行了一些创建和启动任务的代码,但任务从未启动(它只是将状态更改为 WaitingToRun)

      在尝试了该线程中的其他选项无济于事后,我想了更多,并意识到调用此方法的代码本身是在连续任务中调用的,该任务已指定在 UI 任务上运行调度程序(因为它需要更新 UI)...

      类似

      void Main()
      {
          var t1 = new Task(() => Console.WriteLine("hello, I'm task t1"));
          t1.ContinueWith(t => CreateAndRunASubTask(), TaskScheduler.FromCurrentSynchronizationContext());
          t1.Start();
      
          Console.WriteLine("All tasks done with");
      }
      
      // Define other methods and classes here
      public void CreateAndRunASubTask()
      {
          var tsk = new Task(() => Console.WriteLine("hello, I'm the sub-task"));
          tsk.Start();
          Console.WriteLine("sub-task has been told to start");
          tsk.Wait();
          // the code blocks on tsk.Wait() indefinately, the tsk status being "WaitingToRun"
          Console.WriteLine("sub-task has finished");
      }
      

      修复结果非常简单 - 在指定延续任务时,您需要指定 TaskContinuationOption:TaskContinuationOptions.HideScheduler

      这具有...的效果(取自 XML 注释)

      通过调用方法指定由延续创建的任务 例如 System.Threading.Tasks.Task.Run(System.Action) 或 System.Threading.Tasks.Task.ContinueWith(System.Action{System.Threading.Tasks.Task}) 查看默认调度程序 (System.Threading.Tasks.TaskScheduler.Default) 而不是
      而不是作为当前调度程序运行此延续的调度程序。

      即(在我的示例中)

      t1.ContinueWith(t => 
          CreateAndRunASubTask(), 
          System.Threading.CancellationToken.None, 
          TaskContinuationOptions.HideScheduler,
          TaskScheduler.FromCurrentSynchronizationContext());
      

      希望这对某人有所帮助,因为它困扰了我好一阵子!

      【讨论】: