9-多线程与异步

本篇博客对应视频讲解

回顾

上一篇内容讲了如何进行http网络请求。最核心的还是HttpClient类,配合HttpRequestMessageHttpResponseMessage类可以自定义请求内容以及处理返回内容。当然在实际的项目中使用,我们还可以借助其他的类库。不过我们仍然要掌握最基础的用法。

简说异步

异步只是一个概念,相对于同步的概念。

好比操作系统,早期是单用户,之后支持了多用户。支持多用户,可不是说我创建多个用户名,用不同的用户登录这么简单。比如机器A上有两个用户分别为Admin与User。那么我用电脑B使用用户Admin远程登录机器A,你用电脑C使用User用户登录机器A。这样,在机器A上,同时有两个用户登录,这两个用户可以同时进行操作,比如我浏览网页,你观看电影,互不影响。

对于程序来说,一个进程,代码要按照顺序去执行。这就会造成很多问题。比如我们的程序要下载一个文件,那么在下载完这个文件前(不取消),我们无法进行其他的操作。这个时候我们就要借助线程去处理。我们让下载文件这个操作在另一个线程当中去执行,这样就不会造成当前操作的阻塞。

关于C#的异步相关概念,可阅读官方文档进一步的理解。里面有几个示例能帮助我们更好理解。 在C#中,为了简化异步的操作,多出了asyncawait关键词,以及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#中的异步编程,我们可以更好的组织我们的代码。多线程很多作用,最常见的用法:

  1. 防止主线程阻塞。将一些费时操作(如网络请求、计算、I/O读写等)放到非主线程中去操作。我们日常使用的程序基本都有一个主线程(通常是UI线程,用来与用户进行交互)和其他若干线程组成。

  2. 另一种是加快处理速度,比如进行批量操作时,我们可以一个一个来执行,也可以将任务分给多个线程一起执行,这样会大大加快处理速度。

我们来通过一个示例说明: 我们来批量下载一些图片,比如美女图片、高清壁纸等。

    /// <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();
posted @ 2018-10-11 17:31 MSDev_NilTor 阅读(...) 评论(...) 编辑 收藏