如何知道创建的线程数并相应地限制任务

时间:2014-10-28 13:25:18

标签: c# multithreading asynchronous async-await threadpool

很明显,将Taskasync/await而不是Thread一起使用是进行异步调用的方法。我的问题是,有没有办法监视在完成这些任务时产生的线程?这样我就可以决定要调度的最佳任务数,以便线程不会同时消耗大量的CPU周期(假设任务是CPU密集型的)。

让我们举一个例子(也提到输出)。虽然程序在5秒内完成,但它创建了两个线程(Id = 1,4)来完成所有任务。如果我将任务数量增加到6而不是2,则会创建 4个线程。我知道这些CLR线程映射到OS线程(在我的机器中总共4个),但我想知道它们是如何映射的(以及任务)和相应的CPU利用率。有没有办法实现这个目标?

测试代码

    static void Main(string[] args)
    {
        RunTasksWithDelays().Wait();
    }

    static async Task RunTasksWithDelays()
    {
        Stopwatch s = Stopwatch.StartNew();

        Console.WriteLine("ThreadId=" + Thread.CurrentThread.ManagedThreadId);
        Task task1 = LongRunningTask1();
        Task task2 = LongRunningTask2();

        await Task.WhenAll(task1, task2);
        Console.WriteLine("total seconds elapsed:  " + s.ElapsedMilliseconds/1000);
    }

    static async Task LongRunningTask1()
    {
        Console.WriteLine("1 start " + DateTime.Now);
        Console.WriteLine("ThreadId=" + Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(5000);
        Console.WriteLine("ThreadId=" + Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine("1 end " + DateTime.Now);
    }

    static async Task LongRunningTask2()
    {
        Console.WriteLine("2 start " + DateTime.Now);
        Console.WriteLine("ThreadId=" + Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(2000);
        Console.WriteLine("ThreadId=" + Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine("2 end " + DateTime.Now);
    }

输出

ThreadId=1
1 start 28-10-2014 18:27:03
ThreadId=1
2 start 28-10-2014 18:27:03
ThreadId=1
ThreadId=4
2 end 28-10-2014 18:27:05
ThreadId=4
1 end 28-10-2014 18:27:08
total seconds elapsed:  5
Press any key to continue . . .

3 个答案:

答案 0 :(得分:3)

  

使用带有async / await的Task而不是Thread是进行异步调用的方法...(假设任务是CPU密集型的)。

异步(通常是I / O绑定)任务不是CPU密集型的。所以你不必担心它。

如果正在进行CPU密集型工作,请查看Parallel / Parallel LINQ或TPL Dataflow,这两个版本都有内置的限制选项。 TPL Dataflow特别适用于混合I / O和CPU密集型代码。

答案 1 :(得分:0)

这些async方法不会创建新主题。他们使用来自ThreadPool的线程(默认情况下。您可以另外指定)。如果您想知道池中保存了多少个线程,可以使用ThreadPool.GetMaxThreads

int workerThreads;
int completionPortThreads;
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
Console.WriteLine(workerThreads);
  

当GetMaxThreads返回时,workerThreads指定的变量包含线程池中允许的最大工作线程数,而completionPortThreads指定的变量包含线程池中允许的最大异步I / O线程数。

     

您可以使用GetAvailableThreads方法确定任何给定时间线程池中的实际线程数。

关于并行度,真正CPU密集型工作的最佳数量通常是机器具有的逻辑内核数量,因为更多线程只会增加冗余上下文切换

答案 2 :(得分:0)

TPL不会在您生成任务时创建线程,它就是ThreadPool。

假设4-CPU机器上有4个系统线程是错误的。最多有4个线程在4 CPU处理机上同时调度其时间片,但系统中的线程对象数要高得多。这些对象的创建和维护成本很高。

如果线程在进行阻塞调用时处于休眠状态,则会创建更多线程池线程,并且CPU使用率将被去饱和。然后,如果你在ThreadPool上排队了项目,那么最好让处理器饱和,创建更多线程。

如果你想练习ThreadPool实际上在测试中创建更多线程,你应该使用CPU饱和的东西,比如while(counter-- > 0);。只有这样你才能获得更多的线程。 await Task.Delay(2000);不需要更多线程来处理,因为没有项目实际排队到ThreadPool,并且池中总有足够的线程等待处理队列。

如果你做得对,有可能所有非cpu密集型任务(例如io-bound)处理最终都在一个或两个线程上进行处理,这很好,实际上是异步处理的目的

以下代码是cpu密集型的,并且始终在我的计算机上打印与处理器数相同数量的托管线程:

    static void Main(string[] args)
    {
        RunTasksWithDelays();
    }

    static void RunTasksWithDelays()
    {
        var s = Stopwatch.StartNew();
        var tasks = Enumerable.Range(0, 50).Select(i => LongRunningTask()).ToArray();

        // Don't need explicit wait, .Result does it effectively.
        Console.WriteLine(tasks.SelectMany(t => t.Result).Distinct().Count());
        Console.WriteLine(s.Elapsed);
        Console.WriteLine(Environment.ProcessorCount);
    }

    static async Task<List<int>> LongRunningTask()
    {
        await Task.Yield(); // Force task to complete asyncronously.
        var threadList = new List<int> {Thread.CurrentThread.ManagedThreadId};
        var count = 200000000;
        while (count-- > 0) ;
        threadList.Add(Thread.CurrentThread.ManagedThreadId);
        return threadList;
    }