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#教程。
