任务列表同步启动它们 - 我希望它们能够立即启动它们

时间:2017-04-06 09:56:03

标签: c# task

=SUM(COUNTIF(INDIRECT({"M3","P3","S3"}),">0"))                                  

上述函数应该启动多个private async Task MainTask(CancellationToken token) { List<Task> tasks = new List<Task>(); do { var data = StaticVariables.AllData; foreach (var dataPiece in data) { tasks.Add((new Task(() => DoSomething(data)))); } Parallel.ForEach(tasks, task => task.Start()); await Task.WhenAll(tasks); tasks.Clear(); await Task.Delay(2000); } while (!token.IsCancellationRequested); } 方法并同时运行它们。 DoSomething(task)在返回DoSomething之前超时为2秒。经过一些测试,似乎是

之间的部分

false

await Task.WhenAll(tasks);

大约需要2秒*的任务。所以看起来他们会这样做:

  • 启动任务
  • 在2秒后完成或中止
  • 开始下一个任务
  • ...

我怎么能这样做才能同时开始并同时进行操作?

修改

这样做:

tasks.Clear()

导致可怕的性能(完成旧代码大约需要25秒,完成此代码需要115秒)

2 个答案:

答案 0 :(得分:2)

您在此处看到的问题是由于线程池维护了准备运行的最小线程数。如果线程池需要创建比该最小线程更多的线程,则在创建每个新线程之间会引入故意的1秒延迟。

这样做是为了防止诸如“线程踩踏”之类的东西淹没系统并同时创建许多线程。

您可以使用the ThreadPool.SetMinThreads() method更改最低线程限制。但是,建议不要这样做,因为它会破坏预期的线程池操作,并可能导致其他进程变慢。

如果你真的必须这样做,这是一个示例控制台应用程序:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static Stopwatch sw = Stopwatch.StartNew();

        static void Main()
        {
            runTasks();
            setMinThreadPoolThreads(30);
            runTasks();
        }

        static void setMinThreadPoolThreads(int count)
        {
            Console.WriteLine("\nSetting min thread pool threads to {0}.\n", count);
            int workerThreads, completionPortThreads;
            ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
            ThreadPool.SetMinThreads(count, completionPortThreads);
        }

        static void runTasks()
        {
            var sw = Stopwatch.StartNew();
            Console.WriteLine("\nStarting tasks.");
            var task = test(20);
            Console.WriteLine("Waiting for tasks to finish.");
            task.Wait();
            Console.WriteLine("Finished after " + sw.Elapsed);
        }

        static async Task test(int n)
        {
            var tasks = new List<Task>();

            for (int i = 0; i < n; ++i)
                tasks.Add(Task.Run(new Action(task)));

            await Task.WhenAll(tasks);
        }

        static void task()
        {
            Console.WriteLine("Task starting at time " + sw.Elapsed);
            Thread.Sleep(5000);
            Console.WriteLine("Task stopping at time " + sw.Elapsed);
        }
    }
}

如果你运行它,你会在输出中看到在设置最小线程池大小之前运行test(),任务将花费大约10秒(并且你会看到任务开始时间之间的延迟增加在最初的几个任务之后)。

将最小线程池线程设置为30后,新任务启动之间的延迟要短得多,并且运行test()的总时间下降到大约5秒(在我的PC上 - 你的可能会有所不同!)

但是,我只想重申,设置最小线程池大小不是正常的事情,应该谨慎对待。正如Microsoft文档所说:

  

默认情况下,最小线程数设置为系统上的处理器数。您可以使用SetMinThreads方法来增加最小线程数。但是,不必要地增加这些值可能会导致性能问题。如果太多任务同时启动,则所有任务可能看起来都很慢。在大多数情况下,线程池将使用自己的算法来分配线程,从而表现更好。将最小值降低到小于处理器数量也会影响性能。

答案 1 :(得分:1)

首先,您应该使用Task.Run而不是在单独的步骤中创建和启动任务。

你可以在循环或Linq风格中这样做。如果您使用Linq,只需确保您不会遇到延迟执行,其中第二个任务仅在第一个任务完成后才开始。创建所选任务的列表,数组或其他持久集合:

await Task.WhenAll(data.Select(dataPiece => Task.Run(() => DoSomething(dataPiece)).ToList());

另一个问题是DoSomething的内容。只要这是一个同步方法,它就会阻塞它的执行线程,直到完成为止。对于固有的异步操作(比如ping一些网络地址),重新设计方法可以防止这种线程阻塞行为。

Matthew Watson回答的另一个选择是增加可用线程的数量,因此每个任务都可以在自己的线程中运行。这不是最好的选择,但是如果你有许多任务在没有实际工作的情况下有很长的阻塞时间,那么更多线程将有助于完成工作。

如果任务实际使用可用的物理资源,CPU或IO绑定工作,则更多线程将无法帮助。