1 public partial class AsyncForm : Form 2 { 3 Label label; 4 Button button; 5 public AsyncForm() 6 { 7 InitializeComponent(); 8 label = new Label { Location = new Point(this.Size.Width / 2, 20), Text = "Length" }; 9 button = new Button { Location = new Point(this.Size.Width / 2, 50), Text = "Click" }; 10 button.Click += DisPlayWebSiteLength; 11 AutoSize = true; 12 Controls.Add(label); 13 Controls.Add(button); 14 } 15 async void DisPlayWebSiteLength(object sender, EventArgs e) 16 { 17 label.Text = "Fetching"; 18 using (HttpClient client = new HttpClient()) 19 { 20 Task<string> task = client.GetStringAsync("https://www.baidu.com/"); 21 string text = await task; 22 label.Text = text.Length.ToString(); 23 } 24 } 25 }
注意, task 的类型是 Task<string> ,而 await task 表达式的类型是 string 。也就是说,
await 表达式执行的是“拆包”(unwrap)操作,至少在被等待的值为 Task<TResult> 时是这样。
(还可以等待其他类型,但 Task<TResult> 是一个不错的起点。)这是 await 的一个方面,看上
去跟异步编程没什么直接关系,但却让生活更加轻松。
await 的主要目的是在等待耗时操作完成时避免阻塞。它是如何在具体线程中工作的呢?我
们在方法的开始和结束处都设置了 label.Text ,所以可以很自然地认为这两条语句都在UI线程
执行。但显然我们在等待页面下载的时候没有阻塞UI线程。
后续操作 后续操作指在异步操作(或任何 Task )完成时执行的回调程序。在异步方法
中,后续操作保留了方法的控制状态。就像闭包保留了环境中的变量一样,后续操作记
住了它的位置,因此在执行时可回到原处。 Task 类包含一个专门用于添加后续操作的方
法,即 Task.ContinueWith 。
如果在 await 表达式后的代码中加入断点(假设 await 表达式需要后续操作),你会发现堆
栈跟踪中不再拥有 Button.OnClick 方法,该方法早已执行完毕。现在的调用栈是纯粹的
Windows Forms事件循环,在顶部还有一些异步基本结构。如果为了适时地更新UI,而在后台线
程中调用 Control.Invoke ,将得到跟现在非常类似的调用栈,然而现在这些工作已经完全为我
们做好了。起初发现调用栈在眼皮底下发生如此显著的变化时,你可能会有些沮丧,但这对异步
编程来说是绝对必要的。
15.2.2 异步方法
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 PrintPageLength(); 6 7 Console.ReadKey(); 8 } 9 10 static void PrintPageLength() 11 { 12 Task<int> task = GetPageLengthAsync("https://www.baidu.com/"); 13 Console.WriteLine(task.Result); 14 } 15 16 async static Task<int> GetPageLengthAsync(string url) 17 { 18 using (HttpClient client = new HttpClient()) 19 { 20 Task<string> task = client.GetStringAsync(url); 21 int lenth = (await task).Length; 22 return lenth; 23 24 } 25 } 26 }