ЖДИТЕ загрузки нескольких файлов с помощью DownloadDataAsync

У меня есть создатель zip-файла, который принимает String[] URL-адресов и возвращает zip-файл со всеми файлами в String[]

Я полагал, что будет несколько примеров этого, но я не могу найти ответ на вопрос «Как асинхронно загрузить много файлов и вернуться, когда закончите»

Как загрузить {n} файлов одновременно и вернуть словарь только после завершения всех загрузок?

private static Dictionary<string, byte[]> ReturnedFileData(IEnumerable<string> urlList)
{
    var returnList = new Dictionary<string, byte[]>();
    using (var client = new WebClient())
    {
        foreach (var url in urlList)
        {
            client.DownloadDataCompleted += (sender1, e1) => returnList.Add(GetFileNameFromUrlString(url), e1.Result);
            client.DownloadDataAsync(new Uri(url));
        }
    }
    return returnList;
}

private static string GetFileNameFromUrlString(string url)
{
    var uri = new Uri(url);
    return System.IO.Path.GetFileName(uri.LocalPath);
}

person Wesley    schedule 18.08.2014    source источник


Ответы (2)


  • Во-первых, вы пометили свой вопрос async-await, фактически не используя его. На самом деле больше нет причин использовать старые асинхронные парадигмы.
  • Чтобы асинхронно дождаться завершения всех параллельных операций async, вы должны использовать Task.WhenAll, что означает, что вам нужно хранить все задачи в какой-то конструкции (например, в словаре) до фактического извлечения их результатов.
  • В конце, когда у вас есть все результаты, вы просто создаете новый словарь результатов, анализируя uri в имени файла и извлекая результат из async задач.

async Task<Dictionary<string, byte[]>> ReturnFileData(IEnumerable<string> urls)
{
    var dictionary = urls.ToDictionary(
        url => new Uri(url),
        url => new WebClient().DownloadDataTaskAsync(url));

    await Task.WhenAll(dictionary.Values);

    return dictionary.ToDictionary(
        pair => Path.GetFileName(pair.Key.LocalPath),
        pair => pair.Value.Result);
}
person i3arnon    schedule 18.08.2014
comment
Я отметил это правильно, но когда я включаю его в код, я получаю ошибки потоков. Я заменил код ASYNC этим образцом и могу правильно вернуть Byte[], но не могу привести объект к вашему шаблону: stackoverflow.com/questions/8874477/ - person Wesley; 19.08.2014
comment
@Wesley Этот образец создает новый WebClient для каждого запроса, где вы этого не сделали. WebClient не является потокобезопасным (и, очевидно, не поддерживает одновременные асинхронные вызовы). - person i3arnon; 19.08.2014
comment
Спасибо за попытку @|3arnon. Кажется, это больше, чем создание WebClient, так как метод все еще пытается вернуться до завершения. В другом примере мы создаем коллекцию задач и вызываем WhenAll непосредственно в коллекции Task перед выполнением вызова LINQ для возвращенных результатов. Возможно, мне просто нужно немного повозиться с этим и вернуться, если у вас нет другого подхода. - person Wesley; 19.08.2014
comment
@Wesley Здесь происходит то же самое (только задачи находятся в словаре, а не в IEnumerable). Метод не возвращается до завершения, потому что Task.WhenAll ожидает завершения всех задач, а если бы этого не произошло, то это сделал бы Task.Result. - person i3arnon; 19.08.2014
comment
HttpClient.GetByteArrayAsync будет лучше выбор, потому что HttpClient является потокобезопасным, вы можете использовать один экземпляр для всех вызовов, и у вас есть только один экземпляр для удаления - person Panagiotis Kanavos; 21.08.2014

Вам понадобится метод task.WaitAll...

ссылка msdn

Создавайте каждую загрузку как отдельную задачу, а затем передайте их как коллекцию.

Ярлыком для этого может быть обертывание метода загрузки в задачу.

Return new Task<downloadresult>(()=>{ method body});

Извиняюсь за неточность, работа на iPad — отстой для кодирования.

ИЗМЕНИТЬ:

Другая реализация этого, которую, возможно, стоит рассмотреть, — это упаковка загрузок с использованием параллельной структуры.

Поскольку все ваши задачи делают одно и то же, принимая параметр, вместо этого вы можете использовать Parallel.Foreach и обернуть это в одну задачу:

public System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, byte[]>> DownloadTask(System.Collections.Generic.IEnumerable<string> urlList)
        {
            return new System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, byte[]>>(() =>
            {
                var r = new System.Collections.Concurrent.ConcurrentDictionary<string, byte[]>();
                System.Threading.Tasks.Parallel.ForEach<string>(urlList, (url, s, l) =>
                {
                    using (System.Net.WebClient client = new System.Net.WebClient())
                    {
                        var bytedata = client.DownloadData(url);
                        r.TryAdd(url, bytedata);
                    }
                });


                var results = new System.Collections.Generic.Dictionary<string, byte[]>();
                foreach (var value in r)
                {
                    results.Add(value.Key, value.Value);
                }

                return results;
            });
        }

Это использует параллельную коллекцию для поддержки параллельного доступа в методе перед преобразованием обратно в IDictionary.

Этот метод возвращает задачу, поэтому его можно вызывать с ожиданием.

Надеюсь, что это обеспечивает полезную альтернативу.

person kidshaw    schedule 18.08.2014
comment
Похоже, мне нужно заменить DownloadDataAsync на DownloadDataTaskAsync. Этот ответ говорит мне: «Иди, изучай асинхронный стек MSFT и возвращайся, когда узнаешь больше». Могу ли я получить пример с более подробной информацией? - person Wesley; 19.08.2014
comment
@ Уэсли Ты прав; это не подходящий ответ. Конечно, ваш вопрос звучит примерно так: «Мне неинтересно учиться писать асинхронную программу, так что напишите ее для меня, чтобы мне не пришлось учиться этому». что также не подходит. - person Servy; 19.08.2014
comment
Альтернатива не является асинхронной и поэтому менее масштабируемой. - person i3arnon; 19.08.2014
comment
Он возвращает тип задачи, которую можно ожидать, что делает ее асинхронной. Затем внутри он обрабатывает входную коллекцию в параллельных потоках. - person kidshaw; 19.08.2014
comment
@kidshaw Фактическая операция ввода-вывода (DownloadData) является синхронной, поэтому вы поддерживаете эти потоки на протяжении всей операции. Это вредит масштабируемости. - person i3arnon; 21.08.2014
comment
Они составляют часть параллельного тела, поэтому каждый веб-клиент удаляется в конце итерации. Каждая итерация обрабатывается параллельной библиотекой, при этом новые потоки создаются по мере необходимости — в ней часто будет гораздо меньше потоков, чем элементов. Важно отметить, что потоки управляются и размещаются внутри параллельной структуры — я не вижу здесь ничего, что могло бы привести к удержанию потока. - person kidshaw; 21.08.2014