9-多线程与异步
本篇博客对应视频讲解
回顾
上一篇内容讲了如何进行http网络请求。最核心的还是HttpClient
类,配合HttpRequestMessage
与HttpResponseMessage
类可以自定义请求内容以及处理返回内容。当然在实际的项目中使用,我们还可以借助其他的类库。不过我们仍然要掌握最基础的用法。
简说异步
异步只是一个概念,相对于同步的概念。
好比操作系统,早期是单用户,之后支持了多用户。支持多用户,可不是说我创建多个用户名,用不同的用户登录这么简单。比如机器A上有两个用户分别为Admin与User。那么我用电脑B使用用户Admin远程登录机器A,你用电脑C使用User用户登录机器A。这样,在机器A上,同时有两个用户登录,这两个用户可以同时进行操作,比如我浏览网页,你观看电影,互不影响。
对于程序来说,一个进程,代码要按照顺序去执行。这就会造成很多问题。比如我们的程序要下载一个文件,那么在下载完这个文件前(不取消),我们无法进行其他的操作。这个时候我们就要借助线程去处理。我们让下载文件这个操作在另一个线程当中去执行,这样就不会造成当前操作的阻塞。
关于C#的异步相关概念,可阅读官方文档进一步的理解。里面有几个示例能帮助我们更好理解。
在C#中,为了简化异步的操作,多出了async
和 await
关键词,以及Task
类等。因为进行多线程编码在之前是比较繁琐和容易出问题的。
我们还是通过一个简单的示例进行说明。
以下程序需要将项目支持语言设置为最新。才能支持async Main
方法。
static async Task Main(string[] args) { Console.WriteLine("程序运行"); //DoSomethings(); //异步执行 await DoSomethings(); //同步,等待完成 Console.WriteLine("用户操作"); Console.ReadLine(); } static async Task<string> DoSomethings() { Console.WriteLine("开始获取数据..."); // 进行网络请求,通常是费时操作 using (var wc = new WebClient()) { var result = await wc.DownloadStringTaskAsync("https://www.baidu.com"); Console.WriteLine("获取成功"); return ""; } }
以上代码,第4行与第5行分别是两种方式,分别使用这两种方式,观察输出结果的不同。
多线程
多线程编程向来是比较繁琐和容易出错的,借助C#中的异步编程,我们可以更好的组织我们的代码。多线程很多作用,最常见的用法:
防止主线程阻塞。将一些费时操作(如网络请求、计算、I/O读写等)放到非主线程中去操作。我们日常使用的程序基本都有一个主线程(通常是UI线程,用来与用户进行交互)和其他若干线程组成。
另一种是加快处理速度,比如进行批量操作时,我们可以一个一个来执行,也可以将任务分给多个线程一起执行,这样会大大加快处理速度。
我们来通过一个示例说明: 我们来批量下载一些图片,比如美女图片、高清壁纸等。
/// <summary> /// 获取图片地址 /// </summary> /// <returns></returns> static List<string> GetImageLinks() { var imageLinks = new List<string>(); // 下载多个页面内容 for (int page = 0; page < 5; page++) { using (var wc = new WebClient()) { // 获取网页内容 var xmlStr = wc.DownloadString("https://bing.ioliu.cn/?p=" + page); // 解析html var doc = new HtmlDocument(); doc.LoadHtml(xmlStr); // 使用linq获取图片地址 var Links = doc.DocumentNode .Descendants("div") .Where(d => d.Attributes["class"].Value == "card progressive") .Select(s => { var link = s.Element("img").Attributes["src"].Value; return link.Replace("400x240", "1920x1080"); }) // 去重 .Distinct() .ToList(); imageLinks.AddRange(Links); } } return imageLinks.Distinct().ToList(); } /// <summary> /// 下载图片 /// </summary> /// <param name="link"></param> static async Task DownloadImageAsync(string link) { string fileName = link.Substring(link.LastIndexOf("/") + 1); string fullPath = Path.Combine(@"e:\images", fileName); using (var wc = new WebClient()) { try { await wc.DownloadFileTaskAsync(new Uri(link), fullPath); Console.WriteLine("下载" + fileName + "完成"); } catch (Exception) { Console.WriteLine("保存出错:" + fullPath); } } }
使用方法:
// 先获取图片链接 var links = GetImageLinks(); // 记录用时 var watch = new Stopwatch(); var tasks = new List<Task>(); // 计时开始 watch.Start(); // 下载图片 foreach (var link in links) { tasks.Add(DownloadImageAsync(link)); } // 等待所有任务执行完毕 Task.WaitAll(tasks.ToArray()); watch.Stop(); Console.WriteLine("总共用时:" + watch.ElapsedMilliseconds / 1000.0 + "秒"); Console.ReadLine();