Я пытаюсь разработать приложение .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 напрямую. Это приводит к другому исключению:
Невозможно прочитать данные из транспортного соединения: соединение было закрыто
Независимо от того, синхронно или асинхронно. Кажется, это доказательство моего первого подозрения. Но все же я не знаю, как решить эту проблему.