#c# #parallel-processing #task #webclient
#c# #параллельная обработка #задача #webclient
Вопрос:
Я работаю над приложением C # winforms, и у меня есть около 84 URL-адресов, которые я хочу проанализировать с помощью html agility pack
для 84 записей требуется 150 секунд, чтобы выполнить задание с помощью приведенного ниже кода.
Мне было интересно, какие варианты у меня есть, чтобы ускорить его выполнение? любая помощь очень ценится!
Ниже приведена моя структура кода для выполнения этой работы
public class URL_DATA
{
public string URL { get; set; }
public HtmlDocument doc { get; set; }
}
then I call the below function to do the job
public async Task ProcessUrls(string cookie)
{
var tsk = new List<Task>();
//UrlsToProcess is List<URL_DATA>
UrlsToProcess.ForEach(async data =>
{
tsk.Add(Task.Run(async () =>
{
var htmToParse = await ScrapUtils.GetAgilityDocby(cookie, data.URL);
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(htmToParse);
data.doc = htmlDoc;
}));
});
await Task.WhenAll(tsk).ConfigureAwait(false);
}
и, наконец, ниже приведен метод, который я использую для получения строки запроса.
public static async Task<string> GetAgilityDocby(string cookie, string url)
{
using (var wc = new WebClient())
{
wc.Proxy = null;// WebRequest.DefaultWebProxy;// GlobalProxySelection.GetEmptyWebProxy();
wc.Headers.Add(HttpRequestHeader.Cookie, cookie);
wc.Headers.Add(HttpRequestHeader.UserAgent,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36");
wc.Encoding = Encoding.UTF8;
test ;
return await wc.DownloadStringTaskAsync(url).ConfigureAwait(false);
}
}
Ответ №1:
Вы используете ForEach
с асинхронным лямбда-выражением. У меня есть подозрение, что это заставляет ваш код выполняться последовательно, а не параллельно, поскольку каждая следующая итерация будет выполнять await.
Итак, что вы можете сделать, чтобы выяснить это наверняка:
- Проверьте максимальное время операции для одного URL-адреса, это время должно соответствовать скорости всего процесса (если у вас достаточно пропускной способности, памяти и процессора).
- Убедитесь, что ваши операции действительно выполняются параллельно. Например. путем вывода счетчика на консоль. Это не должно быть последовательным и выглядеть достаточно случайным
Вы можете изменить свой код создания задачи на этот, например, попробовать:
var allTasks = myUrls.Select(url => Task.Run(() => {yourCode})
Task.WhenAll(allTasks);
Комментарии:
1. В lambda нет функции ожидания, он просто запускает там новые потоки и ожидает в конце.. Это определенно не самый элегантный способ сделать это, но он должен сработать
2. Я бы проверил это с помощью некоторого протоколирования, чтобы быть уверенным. Возможно, вы правы, и это совсем не проблема, но проверка не помешает 🙂 Знаете ли вы, что нет операции, выполнение которой занимало бы очень много времени?
3. Илья использует метод ScrapUtils. GetAgilityDocby является асинхронным, поэтому мне приходится использовать await, поэтому я пробовал и этот способ, но, похоже, для возврата строки из функции требуется 1,5-7 секунд, и цикл ожидает завершения одного запроса, чтобы начать другой
4. Вместо этого вы можете сделать
.Result
. Не уверен, что это правильно, но, по крайней мере, вы будете знать, является ли это параллельным
Ответ №2:
Попробуйте увеличить минимальное количество запущенных потоков на
ThreadPool.SetMinThreads(84,84);
Это должно значительно ускорить процесс.
Что касается создания задачи, на которое указал Илья, я бы порекомендовал вам полностью исключить часть Task.Run / AwaitAll и использовать параллельный механизм, который был разработан именно для такого рода задач:
Parallel.ForEach(UrlsToProcess, data =>
{
var htmToParse = ScrapUtils.GetAgilityDocby(cookie, data.URL);
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(htmToParse);
data.doc = htmlDoc;
});
Комментарии:
1. Где именно я должен это сделать?
2. В любое время перед вызовом вашей функции. Или в startup-коде вашего приложения. Это нужно вызвать только ОДИН раз, поэтому нет необходимости помещать это в функцию ProcessUrls
3. с параллельным. В результате выполнения каждого цикла происходит немедленное завершение функции, в которой находится цикл, продолжение работы с другим кодом во время выполнения цикла в фоновом режиме. Он запускает все запросы, однако каждый запрос заканчивается 1 на 1, это в основном работает как обычный цикл for, за исключением того, что он выполняется в фоновом режиме.
4. Это странно … потому что механизм parallels определенно блокируется, смотрите Здесь (консольная программа .net): pastebin.com/1PWStQ8C не могли бы вы загрузить свой текущий исходный код, чтобы мы могли изучить?
5. Обнаружена проблема: вы не можете асинхронизировать / ожидать параллельно. Но если вы просто опустите это (отредактировали ответ), то это сработает как по волшебству.