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

0 c# youtube asynchronous

我写了一个用于下载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();


        }


    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 5

您的代码正在下载URL并一次将其写入磁盘.它是异步操作,但是串行操作.

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

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

(此代码假定这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); 也很不寻常.