有这样的应用:
static void Main(string[] args)
{
HandleRequests(10).Wait();
HandleRequests(50).Wait();
HandleRequests(100).Wait();
HandleRequests(1000).Wait();
Console.ReadKey();
}
private static async Task IoBoundWork()
{
await Task.Delay(100);
}
private static void CpuBoundWork()
{
Thread.Sleep(100);
}
private static async Task HandleRequest()
{
CpuBoundWork();
await IoBoundWork();
}
private static async Task HandleRequests(int numberOfRequests)
{
var sw = Stopwatch.StartNew();
var tasks = new List<Task>();
for (int i = 0; i < numberOfRequests; i++)
{
tasks.Add(HandleRequest());
}
await Task.WhenAll(tasks.ToArray());
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
在此应用的输出下方:
从我的观点来看,在一种方法中具有CPU绑定和IO绑定的部分是非常常见的情况,例如解析/归档/序列化某些对象并将其保存到磁盘,因此应该可以正常工作。但是在上面的实现中,它的工作速度很慢。你能帮我理解为什么吗?
如果我们在Task中包装CpuBoundWork()的主体,它会显着提高性能:
private static async Task CpuBoundWork()
{
await Task.Run(() => Thread.Sleep(100));
}
private static async Task HandleRequest()
{
await CpuBoundWork();
await IoBoundWork();
}
为什么没有Task.Run它会如此缓慢?为什么我们可以在添加Task.Run后看到性能提升?我们是否应该总是在类似的方法中使用这种方法?
答案 0 :(得分:1)
for (int i = 0; i < numberOfRequests; i++)
{
tasks.Add(HandleRequest());
}
返回的任务是在await
的第一个HandleRequest()
创建的。所以你在一个线程上执行所有CPU绑定代码:for
循环线程。完全序列化,完全没有并行性。
当您将CPU部件包装在任务中时,您实际上将CPU部件作为任务提交,因此它们是并行执行的。
答案 1 :(得分:1)
你正在做的事情就是这样:
|-----------HandleRequest Timeline-----------|
|CpuBoundWork Timeline| |IoBoundWork Timeline|
尝试这样做:
private static async Task HandleRequest()
{
await IoBoundWork();
CpuBoundWork();
}
它具有启动IO工作的优势,在等待时,CpuBoundWork()可以进行处理。您只需等待您需要回复的最后一刻。
时间表看起来有点像这样:
|--HandleRequest Timeline--|
|Io...
|CpuBoundWork Timeline|
...BoundWork Timeline|
另外,在Web环境中谨慎打开额外线程(Task.Run),每个请求已经有一个线程,因此将它们相乘会对可伸缩性产生负面影响。
答案 2 :(得分:0)
您已经指出您的方法应该是异步的,方法是让它返回Task
,但您实际上并没有(完全)异步。您对该方法的实现会执行一系列昂贵,长时间运行,同步工作,然后然后返回给调用者,并以异步方式执行其他一些工作。
然而,该方法的调用者认为它实际异步(完整)并且它不会同步执行昂贵的工作。他们假设他们可以多次调用该方法,让它立即返回,然后继续,但由于你的实现没有立即返回,而是在返回之前做了一堆昂贵的工作,该代码不能正常工作(具体来说,它不能在前一个操作返回之前启动下一个操作,因此同步工作不是并行完成的。)
请注意您的&#34;修复&#34;不是很惯用的。您正在使用异步过同步反模式。而不是使CpuBoundWork
async
并使其返回Task
,尽管是一个CPU绑定操作,它应该保持为HandleRequest
应该处理的指示CPU绑定的工作应该通过调用Task.Run
:
private static async Task HandleRequest()
{
await Task.Run(() => CpuBoundWork());
await IoBoundWork();
}