C# 异步等待性能?
(只是一个理论问题——对于非gui应用程序) 假设我有很多等待的C# 异步等待性能?,c#,performance,async-await,theory,C#,Performance,Async Await,Theory,(只是一个理论问题——对于非gui应用程序) 假设我有很多等待的代码: public async Task<T> ConsumeAsync() { await A(); await b(); await c(); await d(); //.. } 公共异步任务consumerasync() { 等待一个(); 等待b(); 等待c(); 等待d(); //.. }
代码:
public async Task<T> ConsumeAsync()
{
await A();
await b();
await c();
await d();
//..
}
公共异步任务consumerasync()
{
等待一个();
等待b();
等待c();
等待d();
//..
}
每个任务可能需要很短的时间
问题(还是理论问题)
可能会出现这样一种情况,即处理所有这些“释放回线程”和“获取回线程”的总时间(此处为红色和绿色:)
比单个线程花费更多的时间,而单个线程只需少量延迟即可完成所有工作
我的意思是,我想成为最有生产力的人,但是相反,由于所有这些来回转换,我实际上失去了生产力
这种情况会发生吗
这种情况会发生吗
当然。出于这个原因,您应该认真考虑在哪里使用异步代码。通常,您最好将其用于实际执行异步操作的方法(例如,磁盘或网络I/O)。这些操作所花费的时间通常远远超过在线程上调度任务的成本。此外,在操作系统级别,这些类型的操作本质上是异步的,因此您实际上通过使用异步方法删除了一层抽象
即使在这些情况下,除非您能够利用并发性,否则切换到异步代码也可能看不到明显的性能差异。例如,您发布的代码可能看不到真正的性能提升,除非将其更改为以下内容:
await Task.WhenAll(new[]{A(), B(), C(), D(), ...});
是的,这是可能发生的。也不要忘记,任务系统确实有开销,这是您可以编程的所有效率
如果像这样的事情让你变得太疯狂,那么同步开销可能会让你丧命。这就是说:任务的编程效率相当高
但旧规则依然存在:不要使用超颗粒。有时优化会有所帮助。是的,当然可以。创建状态机的所有开销,让控制来回移动,并使用IOCP
线程。但是如上所述,TPL
是非常优化的。例如,请不要忘记,如果您的TaskAwaitable
快速完成,则可能没有开销,并且它将同步执行,这在快速操作中可能经常发生。理论上是的。不正常,在现实世界中
在常见情况下,async
用于I/O绑定操作,与之相比,线程管理的开销是无法检测的。大多数情况下,异步操作要么需要很长时间(与线程管理相比),要么已经完成(例如缓存)。请注意,async
有一个“快速路径”,如果操作已经完成,则该路径将启动,但不会产生线程
有关更多信息,请参阅和。A任务
对象表示挂起操作的延迟结果。如果没有任何挂起的操作,则不必使用任务和async/await
。否则,我相信async
/await
代码通常比它的裸TPLContinueWith
模拟更有效
让我们做一些计时:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
// async/await version
static async Task<int> Test1Async(Task<int> task)
{
return await task;
}
// TPL version
static Task<int> Test2Async(Task<int> task)
{
return task.ContinueWith(
t => t.Result,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
static void Tester(string name, Func<Task<int>, Task<int>> func)
{
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < 10000000; i++)
{
func(Task.FromResult(0)).Wait();
}
sw.Stop();
Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}
static void Main(string[] args)
{
Tester("Test1Async", Test1Async);
Tester("Test2Async", Test2Async);
}
}
}
输出:
Test1Async: 1582ms
Test2Async: 4975ms
Test1Async: 1557ms
Test2Async: 429ms
Test1Async: 4207ms
Test2Async: 4734ms
输出:
Test1Async: 1582ms
Test2Async: 4975ms
Test1Async: 1557ms
Test2Async: 429ms
Test1Async: 4207ms
Test2Async: 4734ms
Test1Async:4207ms
Test2Async:4734ms
现在差别非常小,尽管async
版本的性能仍然稍好一些。然而,我认为这样的收益实际上是可以忽略的,与异步操作的实际成本或在SynchronizationContext.Current!=空
底线是,如果你处理异步任务,如果你有选择的话,那就选择async
/wait
,不是为了性能,而是为了易用性、可读性和可维护性。这是性能和灵活性之间的折衷。你为什么不试试呢?使方法调用非常快(在方法体中不放置任何内容)。然后看看这是否比同步调用需要更多的时间。将代码运行一千次并将结果绘制成图表。但话说回来,你永远不会有空的方法体,是吗<代码>异步
和<代码>等待可能在有两个I/O任务要执行时使用得最好。哦,顺便说一下,不要预先优化代码。一旦出现瓶颈或性能问题,请进行优化。只有当你的积极因素能够带来好处时,你才应该进行预优化。使用异步几乎从来没有更快过。但它更具可扩展性,但这假定异步总是利用线程。柯克沃尔:请澄清。我特别说过,在执行实际上是异步的操作时,应该使用异步。我想不出任何非异步操作可以在不利用线程的情况下转换为异步方法;Erik Meijer曾赞许地与之联系即使是同步执行,由于TPL代码必须运行,也需要更长的时间。请记住,正如OP指出的一个理论问题。@jgauffin:我不完全确定你所说的“TPL代码必须运行”是什么意思。如果你是这么想的话,就不会有调度。我的意思是,如果你使用TPL对代码进行基准测试,并且代码直接执行操作,那么后者会更快,因为TPL会增加一些开销(即使很小)。@jgauffinwait
使用快速路径的代码只使用少量TPL(相当于Task.FromResult()
和Task.Result
)。这就是你的意思吗?它肯定不会做任何类似于Task.Run()
,或类似的事情。我在写入文件时有上千个小的等待。但是有一个8MB的缓冲区。因此,只有当这个缓冲区已满且没有缓冲区时,才会命中真正的等待