Эффективный способ параллельной загрузки огромного количества файлов - PullRequest
2 голосов
/ 14 июля 2020

Пытаюсь закачать огромное количество файлов (картинок) с инте rnet. Я борюсь с async / parallel, потому что

a) Я не могу сказать, есть файл или нет. Я только что получил миллион ссылок либо с одной картинкой (от 300 КБ до 3 МБ), либо со страницей 404 не существует. Поэтому, чтобы избежать загрузки 0-байтового файла, я дважды запрашиваю одну и ту же страницу, один раз для 404, а затем для изображения. Другой способ - загрузить все 0-байтовые файлы и впоследствии удалить миллионы из них - что удерживает windows 10 в этой задаче до тех пор, пока я не перезагружусь.

b) Пока (очень медленная) загрузка выполняется , всякий раз, когда я смотрю на любой из «успешно загруженных файлов», он создается с 0 байтами и не содержит изображения. Что мне нужно изменить, чтобы действительно загрузить файл перед загрузкой следующего?

Как исправить обе эти проблемы? Есть ли лучший способ загрузить тысячи или миллионы файлов (сжатие / создание .zip на сервере невозможно)

           //loopResult = Parallel.ForEach(_downloadLinkList, new ParallelOptions { MaxDegreeOfParallelism = 10 }, DownloadFilesParallel);    
            private async void DownloadFilesParallel(string path)
            {
                string downloadToDirectory = ""; 
                string x = ""; //in case x fails, i get 404 from webserver and therefore no download is needed
                System.Threading.Interlocked.Increment(ref downloadCount);
                OnNewListEntry(downloadCount.ToString() + " / " + linkCount.ToString() + " heruntergeladen"); //tell my gui to update
                try
                {
                    using(WebClient webClient = new WebClient())
                    {
                        downloadToDirectory = Path.Combine(savePathLocalComputer, Path.GetFileName(path)); //path on local computer

                        webClient.Credentials = CredentialCache.DefaultNetworkCredentials;
                        x = await webClient.DownloadStringTaskAsync(new Uri(path)); //if this throws an exception, ignore this link
                        Directory.CreateDirectory(Path.GetDirectoryName(downloadToDirectory)); //if request is successfull, create -if needed- the folder on local pc
                        await webClient.DownloadFileTaskAsync(new Uri(path), @downloadToDirectory); //should download the file, release 1 parallel task to get the next file. instead there is a 0-byte file and the next one will be downloaded
                    }
                }
                catch(WebException wex)
                {
                }
                catch(Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                }
                finally
                {
                    
                }
            }

// изображение sfw, ссылка nsfw введите описание изображения здесь

1 Ответ

2 голосов
/ 15 июля 2020

Вот пример использования HttpClient с ограничением максимального количества одновременных загрузок.

private static readonly HttpClient client = new HttpClient();

private async Task DownloadAndSaveFileAsync(string path, SemaphoreSlim semaphore, IProgress<int> status)
{
    try
    {
        status?.Report(semaphore.CurrentCount);
        using (HttpResponseMessage response = await client.GetAsync(path, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
        {
            if (response.IsSuccessStatusCode) // ignoring if not success
            {
                string filePath = Path.Combine(savePathLocalComputer, Path.GetFileName(path));
                string dir = Path.GetDirectoryName(filePath);
                if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                using (Stream responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                using (FileStream fileStream = File.Create(filePath))
                {
                    await responseStream.CopyToAsync(fileStream).ConfigureAwait(false);
                }
            }
        }
    }
    finally
    {
        semaphore.Release();
    }
}

Параллелизм

client.BaseAddress = "http://somesite";
int downloadCount = 0;
List<string> pathList = new List<string>();
// fill the list here

List<Task> tasks = new List<Task>();
int maxConcurrentTasks = Environment.ProcessorCount * 2; // 16 for me

IProgress<int> status = new Progress<int>(availableTasks =>
{
    downloadCount++;
    OnNewListEntry(downloadCount + " / " + pathList.Count + " heruntergeladen\r\nRunning " + (maxConcurrentTasks - availableTasks) + " downloads.");
});

using (SemaphoreSlim semaphore = new SemaphoreSlim(maxConcurrentTasks))
{
    foreach (string path in pathList)
    {
        await semaphore.WaitAsync();
        tasks.Add(DownloadAndSaveFileAsync(path, semaphore, status));
    }
    try
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex)
    {
        // handle the Exception here
    }
}

Progress здесь просто выполняет обратный вызов в потоке пользовательского интерфейса. Таким образом, Interlocked не требуется внутри и обновлять пользовательский интерфейс безопасно.

В случае. NET Framework (в. NET Core не имеет эффекта, но не требуется), чтобы ускорить процесс, вы можете добавьте эту строку в код запуска приложения

ServicePointManager.DefaultConnectionLimit = 10;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...