C#async/await教程展示了如何在C#中使用async和await关键字。
通过异步编程,我们可以在主程序执行的同时执行任务。async
和await
关键字简化了C#中的异步编程。C#在语言中内置了异步编程模型。
并发编程用于两种任务:I/O密集型任务和CPU密集型任务。从网络请求数据、访问数据库或读取和写入都是IO绑定任务。CPU-boud任务是计算量大的任务,例如数学计算或图形处理。
async
修饰符用于方法、lambda表达式或匿名方法以创建异步方法。async
方法同步运行,直到它到达它的第一个await
运算符,此时该方法在等待的任务完成时被挂起。与此同时,控制权返回给方法的调用者。
await
运算符暂停对封闭的async
方法的计算,直到异步操作完成。当异步操作完成时,await
运算符返回操作结果(如果有)。
如果async
方法不包含await
运算符,则该方法将同步执行。
在C#中,Task
表示并发操作。
C#简单同步例子
在下一个例子中,我们同步执行三个方法。
using System.Diagnostics; var sw = new Stopwatch(); sw.Start(); f1(); f2(); f3(); sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms"); void f1() { Console.WriteLine("f1 called"); Thread.Sleep(4000); } void f2() { Console.WriteLine("f2 called"); Thread.Sleep(7000); } void f3() { Console.WriteLine("f3 called"); Thread.Sleep(2000); }
使用Thread.Sleep
,我们模拟了一些更长的计算。
var sw = new Stopwatch(); sw.Start();
我们用Stopwatch
测量方法的执行时间。
f1(); f2(); f3();
方法是连续调用的。
$ dotnet run f1 called f2 called f3 called elapsed: 13034 ms
在我们的系统上,执行这三个函数需要13秒。
C#简单异步例子
现在,该示例使用async/await关键字重写。
using System.Diagnostics; var sw = new Stopwatch(); sw.Start(); Task.WaitAll(f1(), f2(), f3()); sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms"); async Task f1() { await Task.Delay(4000); Console.WriteLine("f1 finished"); } async Task f2() { await Task.Delay(7000); Console.WriteLine("f2 finished"); } async Task f3() { await Task.Delay(2000); Console.WriteLine("f3 finished"); }
我们测量了三种异步方法的执行时间。
Task.WaitAll(f1(), f2(), f3());
Task.WaitAll
等待所有提供的任务完成执行。
async Task f1() { await Task.Delay(4000); Console.WriteLine("f1 finished"); }
f1
方法使用async
修饰符并返回一个Task
。在方法体内,我们对Task.Delay
使用了await
运算符。
$ dotnet run f3 finished f1 finished f2 finished elapsed: 7006 ms
现在执行需要7秒。另请注意,任务完成的顺序不同。
C#异步主方法
当我们在Main
方法中使用await
运算符时,我们必须用async
修饰符标记它。
using System.Diagnostics; namespace AsyncMain { class Program { static async Task Main(string[] args) { var sw = new Stopwatch(); sw.Start(); Console.WriteLine("task 1"); Task task1 = doWork(); Console.WriteLine("task 2"); Task task2 = doWork(); Console.WriteLine("task 3"); Task task3 = doWork(); await Task.WhenAll(task1, task2, task3); Console.WriteLine("Tasks finished"); sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms"); } static async Task doWork() { await Task.Delay(1500); } } }
在Main
方法中,我们调用了三次doWork
。
$ dotnet run task 1 task 2 task 3 Tasks finished elapsed: 1550 ms
C#异步读取文件
C#有许多内置的异步读取文件的方法。例如,File.ReadAllTextAsync
异步打开一个文本文件,读取文件中的所有文本,然后关闭文件。
using System.Diagnostics; var task1 = File.ReadAllTextAsync("data1.txt"); var task2 = File.ReadAllTextAsync("data2.txt"); var task3 = File.ReadAllTextAsync("data3.txt"); var task4 = File.ReadAllTextAsync("data4.txt"); Console.WriteLine("doing some work"); var tasks = new Task[] { task1, task2, task3, task4 }; Task.WaitAll(tasks); var content1 = await task1; var content2 = await task2; var content3 = await task3; var content4 = await task4; Console.WriteLine(content1.TrimEnd()); Console.WriteLine(content2.TrimEnd()); Console.WriteLine(content3.TrimEnd()); Console.WriteLine(content4.TrimEnd());
在示例中,我们异步读取四个文件。
var content1 = await task1;
使用await
,我们异步解包任务的结果。
C#CPU绑定异步任务
在下一个示例中,我们将处理CPU密集型计算。
var tasks = new List<Task<int>>(); tasks.Add(Task.Run(() => DoWork1())); tasks.Add(Task.Run(() => DoWork2())); await Task.WhenAll(tasks); Console.WriteLine(await tasks[0]); Console.WriteLine(await tasks[1]); async Task<int> DoWork1() { var text = string.Empty; for (int i = 0; i < 100_000; i++) { text += "abc"; } Console.WriteLine("concatenation finished"); return await Task.FromResult(text.Length); } async Task<int> DoWork2() { var text = string.Empty; for (int i = 0; i < 100_000; i++) { text = $"{text}abc"; } Console.WriteLine("interpolation finished"); return await Task.FromResult(text.Length); }
我们有两种计算密集型方法,可以连接字符串十万次。
tasks.Add(Task.Run(() => DoWork1())); tasks.Add(Task.Run(() => DoWork2()));
Task.Run
方法将指定的工作排入队列以在ThreadPool上运行,并返回该工作的任务或Task句柄。
return await Task.FromResult(text.Length);
我们返回与text.Length
连接的字符数。Task.FromResult
创建一个Task,该任务已成功完成并获得指定结果。
C#多个异步请求
HttpClient
类用于发送HTTP请求和接收来自指定资源的HTTP响应。
using System.Text.RegularExpressions; var urls = new string[] { "http://webcode.me", "http://example.com", "http://httpbin.org", "https://ifconfig.me", "http://termbin.com", "https://github.com" }; var rx = new Regex(@"<title>\s*(.+?)\s*</title>", RegexOptions.Compiled); using var client = new HttpClient(); var tasks = new List<Task<string>>(); foreach (var url in urls) { tasks.Add(client.GetStringAsync(url)); } Task.WaitAll(tasks.ToArray()); var data = new List<string>(); foreach (var task in tasks) { data.Add(await task); } foreach (var content in data) { var matches = rx.Matches(content); foreach (var match in matches) { Console.WriteLine(match); } }
我们异步下载给定的网页并打印它们的HTML标题标签。
tasks.Add(client.GetStringAsync(url));
GetStringAsync
向指定的url发送GET请求,并在异步操作中将响应正文作为字符串返回。它返回一个新任务。
Task.WaitAll(tasks.ToArray());
Task.WaitAll
等待所有提供的任务完成执行。
data.Add(await task);
await
解包操作的结果。
$ dotnet run <title>My html page</title> <title>Example Domain</title> <title>httpbin.org</title> <title>termbin.com - terminal pastebin</title> <title>GitHub: Where the world builds software · GitHub</title>
在本文中,我们使用async/await关键字在C#中创建异步程序。
列出所有C#教程。