Асинхронный поток загрузки Base64 в файл - PullRequest
0 голосов
/ 04 сентября 2018

Я пытаюсь разработать приложение .NET, которое будет взаимодействовать с приложением .NET Core Server (которое я не разрабатывал). Основная цель - скачать файл. Поскольку клиентское приложение будет иметь интерфейс WPF, вся загрузка должна выполняться асинхронно.

Из чтения API серверного приложения я знаю, что ответом на мой запрос является строка в кодировке Base64, содержащая содержимое файла.

То, что я хотел сделать, это асинхронная отправка запроса, передача его ответного потока, асинхронное чтение из этого потока в charArray, base64-decode, асинхронная запись в файл (см. Код ниже).

Но Convert.FromBase64CharArray чаще всего завершается ошибкой с исключением

недопустимая длина для массива или строки base-64 char

Иногда это удается, но загрузка заканчивается преждевременно (загруженная длина <общая длина) </p>

Кажется, будто соединение было закрыто слишком рано, но я не совсем уверен, правда ли это.

Что я пытался решить до сих пор:

  • Используя streamReader.ReadToEndAsync (), декодировать всю строку, асинхронная запись: работает, но для загрузки 115 МБ использовалось около 600 МБ ОЗУ
  • Сделать блок асинхронного чтения {чтения, декодирования, записи} вместо асинхронного чтения, декодирования, асинхронной записи: без улучшений
  • Асинхронизация вообще отсутствует: иногда происходит сбой, но не так часто, как в асинхронной версии
  • Использовать FromBase64Transform :: TransformBlock вместо Convert.FromBase64CharArray: загрузка не завершена в разумные сроки, поскольку для inputBlockSize установлено значение 1Byte (загрузка около 115 МБ)
  • Связь через SSH-туннель для пропуска сервера Apache: загрузка даже не началась
  • Клиент и сервер работают на одной машине: вроде бы нормально работает

Некоторые характеристики:

  • Клиент: Windows 7 x64, .NET 4.6.1
  • Сервер: Ubuntu 16.04, Apache 2.4, .NET Core 2.1.4

И наконец: код

Функция, которая запрашивает файл:

private async Task<WebResponse> DoGetRequestAsync(string requestString)
{
    var completeRequestUrl = $"{_options.StoreUrl}/api/{requestString}";

    try
    {
        RequestStarted?.Invoke(true);

        var request = (HttpWebRequest)WebRequest.Create(completeRequestUrl);

        request.ContentType = "text/plain";
        request.Method = "GET";
        var response = await request.GetResponseAsync();

        RequestFinished?.Invoke(true);

        return response;
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }

    return null;
}

Функция обработки ответа:

public async Task<string> DownloadPackage(string vendor, string package)
{
    // declaring some vars

    using (var response = await DoGetRequestAsync(requestString))
    {
        var totalLength = response.ContentLength;
        var downloadedLength = 0;
        var charBuffer = new char[4 * 1024];

        try
        {
            using (var stream = response.GetResponseStream())
            {
                if (stream != null)
                {
                    using (var reader = new StreamReader(stream))
                    using (var fStream = File.Create(filename))
                    {
                        while (!reader.EndOfStream)
                        {
                            var readBytes = await reader.ReadAsync(charBuffer, 0, charBuffer.Length);
                            var decoded = Convert.FromBase64CharArray(charBuffer, 0, readBytes);

                            await fStream.WriteAsync(decoded, 0, decoded.Length);

                            downloadedLength += readBytes;
                            DownloadProgress?.Invoke((float)downloadedLength / totalLength * 100.0f);
                        }
                    }
                }
            }

            if (downloadedLength < totalLength)
            {
                throw new Exception($"Download failed due to a network error. Downloaded {downloadedLength} Bytes.");
            }

            // some follow-up stuff

            return filename;
        }
        catch (Exception e)
        {
            Console.WriteLine("Error!");
            Console.WriteLine(e.Message);
            throw;
        }
    }
}

Есть идеи, что может вызвать ошибку?

EDIT:

Хорошо, я попытался реализовать решение, предложенное Fildor. Поскольку я не удаляю декодированное содержимое вторичного буфера, теперь требуется больше памяти для выполнения загрузки. Но я мог бы опустить StreamReader и читать из Stream напрямую. Это приводит к другому исключению:

Невозможно прочитать данные из транспортного соединения: соединение было закрыто

Независимо от того, синхронно или асинхронно. Кажется, это доказательство моего первого подозрения. Но все же я не знаю, как решить эту проблему.

...