很明显,将Task
与async/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 . . .
答案 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;
}