4

我有一个程序可以处理并行运行的各种任务。单个任务充当各种管理器,确保在运行下一个任务之前满足某些条件。但是,我发现有时某个任务会长时间处于 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延迟我的任务安排是否有原因?我是否饱和了一些基础资源?这里发生了什么?

4

3 回答 3

6

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

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

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

于 2013-02-01T08:52:12.227 回答
0

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

在尝试了此线程中的其他选项无济于事后,我想了更多,并意识到调用此方法的代码本身是在延续任务中调用的,该任务已指定在 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}) 等方法指定由 continuation 创建的任务查看默认调度程序 (System.Threading.Tasks.TaskScheduler.Default) 而
不是作为当前调度程序运行此延续的调度程序。

即(在我的例子中)

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

希望这对某人有所帮助,因为它困扰了我一段时间!

于 2019-11-22T14:01:14.403 回答
0

刚遇到类似的问题。

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

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

_task = new Task(() => DoSmth(_cancellationTokenSource.Token), TaskCreationOptions.LongRunning);
_task.Start();
于 2019-07-02T12:30:21.130 回答