【问题标题】:Why these tasks are starting delayed?为什么这些任务开始延迟?
【发布时间】:2016-03-10 19:35:07
【问题描述】:

我正在尝试这段代码(只是生成一些任务并模拟工作):

var tasks = Enumerable.Range(1, 10).Select(d => Task.Factory.StartNew(() =>
{
    Console.Out.WriteLine("Processing [{0}]", d);
    Task.Delay(20000).Wait(); // Simulate work. Here will be some web service calls taking 7/8+ seconds.
    Console.Out.WriteLine("Task Complete [{0}]", d);
    return (2 * d).ToString();
})).ToList();

var results = Task.WhenAll(tasks).Result;
Console.Out.WriteLine("All processing were complete with results: {0}", string.Join("|", results));

我期待在控制台中立即看到 10 个Processing ...;但是当我运行时,最初我会看到这个输出

Processing [1]
Processing [2]
Processing [3]
Processing [4]

然后1/2秒后Processing [5]Processing [6]等陆续显示。

你能解释一下吗?这是否意味着任务开始延迟?为什么?

【问题讨论】:

  • 您正在创建长时间运行的任务。您应该善待调度程序并找到适当的重载,以便您可以通过TaskCreationOptions.LongRunning通知调度程序这一事实
  • @Damien_The_Unbeliever 哇!刚刚使用了那个重载,ta-da!
  • @Damien_The_Unbeliever 你能发表你的评论作为答案吗(这样我就可以将其标记为已回答)?
  • 发生这种情况的原因是因为默认情况下任务是使用线程池调度的,当线程池线程创建超过ThreadPool.GetMinThreads() 给出的计数时会受到限制。长时间运行的线程没有安排在线程池中,因此它们不会出现这种行为。
  • 现在还有一个问题,我应该使用TaskCreationOptions.LongRunning 选项创建哪个Tasks? >500 毫秒?

标签: c# multithreading task


【解决方案1】:

正如另一个答案中提到的,使用TaskCreationOptions.LongRunning 将解决您的问题。

但这不是您应该解决问题的方式。您的示例模拟了 CPU 限制 工作。您说您的任务将调用 Web 服务 - 这意味着它们将是 IO 绑定

因此,它们应该异步运行。但是,Task.Delay(20000).Wait();同步等待,因此它并不代表将/应该实际发生的事情。

以这个例子代替:

var tasks = Enumerable.Range(1, 10).Select(async d =>
{
    Console.Out.WriteLine("Processing [{0}]", d);
    await Task.Delay(5000); // Simulate IO work. Here will be some web service calls taking 7/8+ seconds.
    Console.Out.WriteLine("Task Complete [{0}]", d);
    return (2*d).ToString();
}).ToList();
var results = Task.WhenAll(tasks).Result;
Console.Out.WriteLine("All processing were complete with results: {0}", string.Join("|", results));

所有任务都按预期立即开始。

【讨论】:

    【解决方案2】:

    我希望你有 4 个 cpu 核心。

    让两个(cpu 绑定)线程争夺一个内核会导致完成工作需要更长的时间,然后让一个线程执行第一个任务,然后运行第二个任务。

    除非任务系统知道其他情况,否则任务系统会假定任务运行时间短且受 CPU 限制,并且它们将使用“无阻塞”IO。

    因此,我希望任务系统默认的线程数接近核心数。

    使用TaskCreationOptions.LongRunning

    向 TaskScheduler 提供一个提示,提示可能会出现超额订阅 保证。超额订阅允许您创建比 可用的硬件线程数。

    最后,任务不是线程,它们旨在向您隐藏线程的许多细节,包括控制正在使用的线程数。创建 100 个线程是合理的任务,如果你创建了 100 多个线程都试图同时运行,cpu 缓存等将非常困难。


    但是,让我们回到您正在尝试做的事情。您的示例模拟了 CPU 密集型工作。您说您的任务将调用 Web 服务 - 这意味着它们将是 IO 绑定的。

    因此,它们应该异步运行。但是,Task.Delay(20000).Wait(); 同步等待,因此它并不代表实际将/应该发生的事情。有关使用 await 进行异步延迟的代码示例,请参阅 Gediminas Masaitis 答案。但是,一旦您使用更多的异步代码,您就需要更多地考虑锁定等问题。

    如果您同时处理 100 个请求,异步 IO 显然会更好。但是,如果您的应用程序中只有“手满”并且没有其他 await 用法,那么 TaskCreationOptions.LongRunning 可能就足够了。

    【讨论】:

    • 由于“更多异步代码”,您不需要锁定。您需要它是因为并发性(更准确地说是共享)。 await 并不比在多个线程上使用同步阻塞代码更重要。即使在单线程应用程序中,同步也很重要,例如,如果涉及重入。
    • @Luaan,在一般情况下同意,但我已经看到很多应用程序可以并行完成对外部服务器的调用(例如 3 个 membase 查找),等待结果之前不断地无需添加任何锁定。将“await”添加到尚未使用它并且完全是单线程的代码库中会增加每个开发人员需要理解的程度。也经常是对 SQL Server 的调用可以在多个连接上运行,SQL 服务器的异步使用从来都不是很好......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-26
    • 1970-01-01
    • 1970-01-01
    • 2022-09-30
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    相关资源
    最近更新 更多