Использование HttpClient для асинхронных загрузок файлов

У меня есть служба, которая возвращает csv-файл в POST-запрос. Я хотел бы загрузить указанный файл, используя асинхронные методы. Хотя я могу получить файл, у моего кода есть несколько нерешенных проблем и вопросов:

1) это действительно асинхронный?

2) есть ли способ узнать длину содержимого, даже если оно отправляется в формате chunked? Думаю, прогресс бары).

3) Как я могу следить за прогрессом, чтобы удержать выход программы до завершения всех работ.

using System;
using System.IO;
using System.Net.Http;

namespace TestHttpClient2
{
    class Program
    {
        /*
         * Use Yahoo portal to access quotes for stocks - perform asynchronous operations.
         */

        static string baseUrl = "http://real-chart.finance.yahoo.com/";
        static string requestUrlFormat = "/table.csv?s={0}&d=0&e=9&f=2015&g=d&a=4&b=5&c=2000&ignore=.csv";

        static void Main(string[] args)
        {
            while (true) 
            {
                Console.Write("Enter a symbol to research or [ENTER] to exit: ");
                string symbol = Console.ReadLine();
                if (string.IsNullOrEmpty(symbol))
                    break;
                DownloadDataForStockAsync(symbol);
            }
        }

        static async void DownloadDataForStockAsync(string symbol)
        {
            try
            {
                using (var client = new HttpClient())
                {
                    client.BaseAddress = new Uri(baseUrl);
                    client.Timeout = TimeSpan.FromMinutes(5);
                    string requestUrl = string.Format(requestUrlFormat, symbol);

                    //var content = new KeyValuePair<string, string>[] {
                    //    };
                    //var formUrlEncodedContent = new FormUrlEncodedContent(content);

                    var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
                    var sendTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                    var response = sendTask.Result.EnsureSuccessStatusCode();
                    var httpStream = await response.Content.ReadAsStreamAsync();

                    string OutputDirectory = "StockQuotes";

                    if (!Directory.Exists(OutputDirectory))
                    {
                        Directory.CreateDirectory(OutputDirectory);
                    }

                    DateTime currentDateTime = DateTime.Now;
                    var filePath = Path.Combine(OutputDirectory, string.Format("{1:D4}_{2:D2}_{3:D2}_{4:D2}_{5:D2}_{6:D2}_{7:D3}_{0}.csv",
                        symbol,
                        currentDateTime.Year, currentDateTime.Month, currentDateTime.Day,
                        currentDateTime.Hour, currentDateTime.Minute, currentDateTime.Second, currentDateTime.Millisecond
                        ));

                    using (var fileStream = File.Create(filePath))
                    using (var reader = new StreamReader(httpStream))
                    {
                        httpStream.CopyTo(fileStream);
                        fileStream.Flush();
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error, try again!");
            }
        }

    }
}

1 ответов


  1. "это действительно асинхронный?"

да, в основном. The DownloadDataForStockAsync() метод вернется до завершения операции, в await response.Content.ReadAsStreamAsync() заявление.

основное исключение находится в конце метода, где вы вызываете Stream.CopyTo(). Это не асинхронно, и потому что это потенциально длительная операция может привести к заметным задержкам. Однако в консольной программе вы не заметите, потому что продолжение метод выполняется в пуле потоков, а не в исходном вызывающем потоке.

если вы собираетесь переместить этот код в GUI-фреймворк, такой как Winforms или WPF, вы должны изменить оператор на read await httpStream.CopyToAsync(fileStream);

  1. есть ли способ узнать длину контента, даже если он отправляется в формате chunked? Думаю, прогресс бары).

предполагая, что сервер включает в себя Content-Length в заголовках (и это должно), да. Это должно быть возможно.

обратите внимание, что если вы используете HttpWebRequest объект ответ будет ContentLength свойство, дающее вам это значение напрямую. Вы используете HttpRequestMessage вот, что мне меньше знакомы. Но насколько я могу судить, Вы должны иметь доступ к Content-Length значение такой:

long? contentLength = response.Content.Headers.ContentLength;

if (contentLength != null)
{
    // use value to initialize "determinate" progress indication
}
else
{
    // no content-length provided; will need to display progress as "indeterminate"
}
  1. как я могу лучше всего отслеживать прогресс, чтобы удержать выход программы, Пока вся работа не будет завершена.

есть много способов. Я укажу, что любой разумный способ потребует, чтобы вы изменили DownloadDataForStockAsync() метод так, что он возвращает Task, а не void. В противном случае у вас нет доступа к созданной задаче. Но ты все равно должен это сделать, так что ничего страшного. :)

самым простым было бы просто сохранить список всех задач, которые вы начинаете, а затем ждать их, прежде чем выйти:

static void Main(string[] args)
{
    List<Task> tasks = new List<Task>();

    while (true) 
    {
        Console.Write("Enter a symbol to research or [ENTER] to exit: ");
        string symbol = Console.ReadLine();
        if (string.IsNullOrEmpty(symbol))
            break;
        tasks.Add(DownloadDataForStockAsync(symbol));
    }

    Task.WaitAll(tasks);
}

конечно, это требует что вы явно поддерживаете список каждого Task объект, включая те, которые уже завершены. Если вы собираетесь работать в течение длительного времени и обрабатывать очень большое количество символов, это может быть запретительно. В этом случае, вы можете предпочесть использовать