Asycn/Await 异步编程初窥
经过两天密集型的学习,翻阅了大量 webpages ,点击了不少重点 blogs,总算基本了解了一些 async/await 搭配使用的入门技巧,总结一下
1. async/await 应该只是语法上的甜点,让想使用异步方法运行程序的程序员能够专心专注代码逻辑,而别被原来的 Begin... End... 型或是IAsync ...Async 的老式异步带的越来越远。想起刚试着编写 Tcp Listener的时候,真是噩梦。异步响应必须放在另一个回调方法或是事件中,而从异步响应产生的异步接收又必须再次放到另一个回调方法或是事件中,更可怕的是,如何循环它们。
2. async/await 标识的方法,“本身不主动创建额外线程”——这句话很容易让人误解。《Async 和 Await 异步编程的原理》这篇文章写的比较详细,异步,必然多线程,更何况是从 UI 线程启动的。只是这种多线程被Framework透明掉了,使用者不必自己在这方面去走脑子怎么创建线程,怎么回收,怎么捕捉异常,还得让代码好看一点。
3. await 让我开始的时候陷入了一个误区,认为使用 var result = await FunRun(...) 这种写法,FunRun()方法就会异步运行而不会阻塞UI线程,这是错的,我忽略了一个很严重的问题。先看一下原来错误的代码:
1 private async Task<TimeSpan> Download(Uri address, string fileName) 2 { 3 this.uri = address; 4 this.fileName = fileName; 5 DateTime start = DateTime.Now; 6 // 7 HttpWebRequest request; 8 HttpWebResponse response = null; 9 try 10 { 11 request = (HttpWebRequest)WebRequest.Create(address); 12 response = (HttpWebResponse)request.GetResponse(); 13 using (Stream httpStream = response.GetResponseStream()) 14 { 15 using (FileStream writer = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read)) 16 { 17 byte[] buffer = new byte[8192]; 18 int readLength = httpStream.Read(buffer, 0, buffer.Length); 19 // 20 while (readLength > 0) 21 { 22 writer.Write(buffer, 0, readLength); 23 readLength = httpStream.Read(buffer, 0, buffer.Length); 24 } 25 } 26 } 27 } 28 catch(Exception) 29 { 30 if (response != null) { response.Close(); } 31 } 32 33 return DateTime.Now - start; 34 }
代码完成下载一个资源到指定位置的功能,本身逻辑没有错误,看起来这似乎是一个标准的带有 async 标记和 Task<T> 返回类型的方法,所以我信心慢慢的调用了它。
1 private async void button1_Click(object sender, EventArgs e) 2 { 3 //略过部分逻辑判断的东西 4 ...... 5 6 TestApp.HttpDownloader.HttpDownloaderEngine hdEngine = new HttpDownloaderEngine(); 7 var time = await hdEngine.Download(address, saveFileTextBox.Text); 8 MessageBox.Show(string.Format("下载完成,共耗时 {0} 秒", time.Seconds.ToString())); 9 }
Button1 的 Click 事件本身也没有问题,我还细心的加上了 async 标记(当然,不加也不让我过去啊),一切很好我即刻运行,结果让我很困惑,的确成功的下载了 Mp3 到我的电脑,可不管时间长短,我的 UI 依旧像同步的一样,不能响应知道执行完成。问题处在哪里?
人是需要休息的,学习->休息->消化,经过再次梳理和查看资料,我找出了第 3 点开头我说的,Bug 在于 HttpDownloadEngine 的 Download 方法我虽然标记了 async,button_click 误以为 Download 方法可以完成异步工作,button_click 是没错的,错在 Download 方法内不,没有任何“异步执行的代码”!也就是说,Download 方法内没有出现一个 await ,这使得标记了 async 的 Download 方法依旧是同步的,button_click 调用了一个同步方法在同步执行。
如何修改呢,在 Download 方法内的关键处执行 await Task<T> ....Async ,如 readLength = await responseStream.ReadAsync(buffer, 0, buffer.Length);,关键处是指的确需要异步执行的地方。
4. 我要感谢 Microsoft,我不是大神,我是个爱好者,是微软借鉴并创造了 .Net 和无与伦比的 VS,让我和像我一样的人都能领略计算机的更多魅力,这种魅力,来自于创造和分享给他人。
贴上一个下载的结果图