在c#中使用async / await和httpclient进行多线程处理

时间:2017-01-06 19:58:32

标签: c# asynchronous youtube

我写了一个用于下载YouTube预览图像的控制台应用程序。但我认为这个程序是同步运行而不是异步。我做错了什么以及如何从web使用async / await创建多个加载文件?

using System;
using System.IO;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;


namespace YoutubePreviewer
{
    class Node
    {
        public string Path { get; private set; }
        public string Title { get; private set; }
        public string Source { get; private set; }
        public string Id { get; private set; }
        public Previews Previews { get; set; }

        public Node(string p, string t, string s, string i)
        {
            Path = p;
            Title = t;
            Source = s;
            Id = i;
        }

    }

    class Previews
    {
        public string[] Urls { get; private set; }


        public static Previews Get(Node n)
        {
            string[] resolutions = {"default", "hqdefault", "mqdefault", "maxresdefault"};
            for (int i = 0; i < resolutions.Length; i++)
            {
                string end = resolutions[i] + ".jpg";
                resolutions[i] = "https://img.youtube.com/vi/" + n.Id + "/" + resolutions[i] + ".jpg";
            }
            Previews pr = new Previews();
            pr.Urls = resolutions;
            return pr;
        }
    }

    static class Operations
    {
        public static async Task<string> DownloadUrl(string address)
        {
            HttpClient http = new HttpClient();
            return await http.GetStringAsync(address);
        }

        public static async Task<Node> Build(string url)
        {
            var source = await Operations.DownloadUrl(url);
            var title = Regex.Match(source, "<title>(.*)</title>").Groups[1].Value;
            var id = Regex.Match(url, @"watch\?v=(.+)").Groups[1].Value;
            Node node = new Node(url, title, source, id);
            node.Previews =await Task<Previews>.Factory.StartNew(()=>Previews.Get(node);
            return node;
        }

        public static async Task WriteToDisk(Node n, string path = "C:/Downloads")
        {
            Console.WriteLine($"Starting downloading {n.Path} previews");
            var securedName = string.Join("_", n.Title.Split(Path.GetInvalidFileNameChars()));

            Directory.CreateDirectory(Path.Combine(path, securedName));
            HttpClient http = new HttpClient();
            foreach (var preview in n.Previews.Urls)
            {
                try
                {
                    var arr = await http.GetByteArrayAsync(preview);
                    await Task.Delay(100);
                    string name = preview.Substring(preview.LastIndexOf("/") + 1);
                    using (FileStream fs = new FileStream(Path.Combine(path, securedName, name), FileMode.Create,
                        FileAccess.ReadWrite))
                    {
                        await fs.WriteAsync(arr, 0, arr.Length);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Can't download and save preview  {preview}");
                    Console.WriteLine(e.Message);
                    Console.WriteLine(new string('*', 12));
                }
                Console.WriteLine($"{preview} is saved!");
            }

        }

        public static async Task Load(params string[] urls)
        {

            foreach (var url in urls)
            {
                Node n = await Build(url);
                await WriteToDisk(n);

            }
        }
    }

    class Program
    {
        static  void Main(string[] args)
        {

            Task t= Operations.Load(File.ReadAllLines("data.txt"));

            Task.WaitAll(t);

            Console.WriteLine("Done");
            Console.ReadKey();


        }


    }
}

3 个答案:

答案 0 :(得分:5)

您的代码正在下载网址并一次将其写入磁盘。它是异步操作,但是连续操作。

如果您希望它以异步方式运行并发,那么您应该使用类似Task.WhenAll的内容:

public static async Task LoadAsync(params string[] urls)
{
  var tasks = urls.Select(url => WriteToDisk(Build(url)));
  await Task.WhenAll(tasks);
}

(此代码假定Build是一个同步方法,应该是这样。)

还有一些不相关的问题突然出现:

  • node.Previews =await Task<Previews>.Factory.StartNew(()=>Previews.Get(node);正在向线程池发送琐碎的工作,没有任何理由。它应该是node.Previews = Previews.Get(node);
  • 这意味着Operations.Build不一定是async,实际上也不应该是HttpClient
  • 您应该使用Task.WaitAll(t);的单个共享实例,而不是为每个请求创建一个新实例。
  • t.Wait();很奇怪。它可以只是await Task.Delay(100);
  • currentId = $('#currentId').attr('value'); google.charts.load('current', {'packages':['corechart']}); google.charts.setOnLoadCallback(drawRevenue); function drawRevenue() { // Define the chart to be drawn. var jsonData = $.ajax({ type: "POST", url: Settings.base_url+"xxController/draw", data: "currentID="+currentId+"&table=cash", dataType:"json", async: false }).responseText; // Create our data table out of JSON data loaded from server. var data = new google.visualization.DataTable(jsonData); // Set options the chart. // This is the part I want to get dynamically from the callback (I have more options) var options = {title:titleRevenueChart}; var chart = new google.visualization.LineChart(document.getElementById('curve_chart')); chart.draw(data, options); } 也很不寻常。

答案 1 :(得分:1)

要添加到@Stephen Cleary的优秀答案 - 正如他所说,这在技术上是异步运行的,但实际上并没有帮助你,因为它是连续执行的 - 即它 是异步但性能并不比它实际上只是同步运行更好。

这里要记住的关键是async / await只会帮助你,如果它实际上允许机器做更多的工作,而不是在一定时间内完成的工作(或者它允许机器完成一个某些任务更快)。

只是用我最喜欢的比喻:假设你和另外9个人在一家餐馆。当服务员来接订单时,他打电话的第一个人还没准备好。显然,最有效的方法是接受其他9个人的命令,然后回到他身边。然而,假设第一个人说:“只要你等我准备好先订购,就可以回到我身边。” (这基本上就是你上面所说的 - “只要你等我先完成下载,就可以回到我以后处理下载的方法了”)。这种类比在任何方面都不完美,但我认为这抓住了这里需要发生的事情的本质。

要记住的关键是,如果服务员能够在相同的时间内完成更多工作或者能够更快地完成某组任务,那么这里只会有一个改进。在这种情况下,如果他减少了他花在桌子上的订单的总时间,他只能节省时间。

还有一点需要记住:在控制​​台应用程序中执行类似Task.WaitAll(...)的操作是可以接受的(只要您没有使用同步上下文),但是您要确保不要执行类似的操作在WPF应用程序中或具有同步上下文的其他内容可能会导致死锁。

答案 2 :(得分:0)

控制并发性非常重要,因此您可以有效地利用网络通道,而不会受到限制。所以我建议使用AsyncEnumerator NuGet Package代码:

using System.Collections.Async;

static class Operations
{
    public static async Task Load(params string[] urls)
    {
        await urls.ParallelForEachAsync(
            async url =>
            {
                Node n = await Build(url);
                await WriteToDisk(n);
            },
            maxDegreeOfParallelism: 10);
    }
}