The performance characteristics of async methods in C#
发表于|更新于
|字数总计:492|阅读时长:2分钟
在最近的两篇博客文章中,我们深入探讨了C#异步方法的内部实现机制,并详细分析了C#编译器提供的扩展点如何调整异步方法的行为。今天,我们将重点研究异步方法的性能特征。
正如本系列第一篇文章所述,编译器进行了大量转换工作,使异步编程体验几乎与同步编程无异。但为了实现这一点,编译器需要创建状态机实例、将其传递给异步方法生成器、调用任务等待器等。显然,所有这些逻辑都会带来性能开销,但具体代价有多大呢?
在TPL(任务并行库)出现之前,异步操作通常粒度较粗,因此其开销往往可以忽略不计。但在现代应用中,即使相对简单的程序每秒也可能执行成百上千次异步操作。TPL虽然针对这种工作负载进行了优化设计,但它并非魔法,仍然存在一定开销。
为了准确测量异步方法的开销,我们将对首篇博客中的示例进行适当调整后作为测试基准。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| public class StockPrices { private const int Count = 100; private List<(string name, decimal price)> _stockPricesCache; public async Task<decimal> GetStockPriceForAsync(string companyId) { await InitializeMapIfNeededAsync(); return DoGetPriceFromCache(companyId); } public decimal GetStockPriceFor(string companyId) { InitializeMapIfNeededAsync().GetAwaiter().GetResult(); return DoGetPriceFromCache(companyId); } public decimal GetPriceFromCacheFor(string companyId) { InitializeMapIfNeeded(); return DoGetPriceFromCache(companyId); } private decimal DoGetPriceFromCache(string name) { foreach (var kvp in _stockPricesCache) { if (kvp.name == name) { return kvp.price; } } throw new InvalidOperationException($"Can't find price for '{name}'."); } [MethodImpl(MethodImplOptions.NoInlining)] private void InitializeMapIfNeeded() { } private async Task InitializeMapIfNeededAsync() { if (_stockPricesCache != null) { return; } await Task.Delay(42); _stockPricesCache = Enumerable.Range(1, Count) .Select(n => (name: n.ToString(), price: (decimal)n)) .ToList(); _stockPricesCache.Add((name: "MSFT", price: 42)); } }
|