StreamReader
, ReadToEndAsync
With ReadToEndAsync
on StreamReader
, we can read in a file without blocking other code execution. But when can this feature lead to a performance gain?
In this test, we perform an expensive, CPU-bound computation while reading in a file. The same computation is done in the Main
method.
Here we have 2 code paths: the first uses async
and await
. The second does not. The code does the same things, but the first iteration in the loop is asynchronous.
ComputeDataFromFileAsync
. It uses Task, async
, await
, and ReadToEndAsync
on StreamReader
.ComputeSum()
. This causes extreme CPU load.using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; class Program { const string _file = @"C:\programs\huge-file"; public static void Main() { // Huge file needed for testing. CreateHugeFile(); // Loop over tests. for (int i = 0; i <= 1; i++) { // Use async on first iteration only. bool useAsync = i == 0; Console.WriteLine("Use async: " + useAsync); var t1 = Stopwatch.StartNew(); if (useAsync) { // Use async code here. Task<int> task = ComputeDataFromFileAsync(); Console.WriteLine(ComputeSum()); task.Wait(); Console.WriteLine("Data: " + task.Result); } else { // Use synchronous code here. int result = ComputeDataFromFile(); Console.WriteLine(ComputeSum()); Console.WriteLine("Data: " + result); } Console.WriteLine("Elapsed: " + t1.ElapsedMilliseconds.ToString()); } } static void CreateHugeFile() { using (StreamWriter writer = new StreamWriter(_file)) { for (int i = 0; i < 10000; i++) { writer.WriteLine("Huge file line"); } } } static double ComputeSum(int lengthArgument = 60000) { // Does many computations based on argument. // ... Meant to be slow. double[] array = new double[lengthArgument]; for (int i = 0; i < array.Length; i++) { array[i] = i; } for (int z = 0; z < 100; z++) { for (int i = 0; i < array.Length; i++) { array[i] = (int)Math.Sqrt(array[i]) + (int)Math.Pow(array[i], 2) + 10; } } return array.Sum(); } static async Task<int> ComputeDataFromFileAsync() { int count = 0; using (StreamReader reader = new StreamReader(_file)) { string contents = await reader.ReadToEndAsync(); count += (int)ComputeSum(contents.Length % 60000); } return count; } static int ComputeDataFromFile() { int count = 0; using (StreamReader reader = new StreamReader(_file)) { string contents = reader.ReadToEnd(); count += (int)ComputeSum(contents.Length % 60000); } return count; } }Use async: True 2230342492166 Data: -2147483648 Elapsed: 522 Use async: False 2230342492166 Data: -2147483648 Elapsed: 805
ComputeSum
With ComputeSum
, we have high CPU usage during file loads. And we call ComputeSum
in main as well—this is where the performance is most affected by a sync code.
ReadToEndAsync
, our program finishes in 522 ms. With ReadToEnd
(no async
) it finishes in 805 ms.async
, await
, Task, and ReadToEndAsync
features lead to a significant speedup in the program.The programs come to the same result—they do the same thing. But async
gives us parallel processing, so the program finishes much faster.
With async
, await
, ReadToEndAsync
and Task, we can achieve a big performance boost. The important thing is that excess CPU usage must be present for this to help.