Ребят, помогите плиз с этой %#$#^ асинхронностью и WPF?
Пишу такой велосипед, что удивляешься, почему он требует так много усилий!
Задача — "красивая" скачка файлов: есть список картинок, они ставятся в очередь и качаются, попутно отображая прогресс.
Очередь ограничена 5 работающими трэдами (хотя и без Семафора те же проблемы).
Как ДОЛЖНО работать: из 10 картинок 5 всегда должны отображать некий прогресс, пока все скачки не закончатся.
Что НЕ ТАК работает: ставлю 10 картинок, в UI — тишина. Ждём где-то 15 секунд и тут UI оживает: 7 скачек тут же отображаются как "готово", а из остальных только ДВЕ качаются (прогресс виден), третья ждёт, потом одна из двух завершается и третья тоже докачивается, причём у всех трёх прогресс был виден!
То есть имеем ряд проблем:
1. Качается только ДВА задания, хотя работающих трэдов — несколько. Такое ощущение, что приложению не дают юзать больше 2 сокетов Кто-то слышал про подобные ограничения?
2. UI не показывает изменения (хотя они есть), нормально начинает работать лишь спустя 15 секунд — что за фигня? Кого и где там ждут?
Сам код — думаю, вы легко разберётесь, везде комменты.
async void Download_Clicked(object sender, RoutedEventArgs e)
{
semDL = new Semaphore(5, 5);
var currTasks = new List<Task>();// здесь собираем все таски, чтобы потом их ждатьforeach (var pic in PicsToLoad) {
while (!semDL.WaitOne(500)) {// limit maximum downloading threads
DoEvents();// даже принудительное обновление UI не помогает!!
}
currTasks.Add(Task.Factory.StartNew(() => {
var web = new WebClient();// может, это WebClient такой уродский? Кто-то сталкивался с его некорректным поведением?
web.DownloadProgressChanged += Web_DownloadProgressChanged;// этот метод явно вызывается - проверено
web.DownloadFileCompleted += Web_DownloadFileCompleted;// этот тоже
web.DownloadFileTaskAsync(new Uri(pic), GeneratedName).Wait();// ждём завершения закачки и освобождаем семафор
semDL.Release();
}));
}
await Task.WhenAll(currTasks.ToArray());
}
public void DoEvents()
{
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (Action)(() => { }));// подсмотрено в тырнетах
}
void Web_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
var url = (Uri)((TaskCompletionSource<object>)e.UserState).Task.AsyncState;
PicStatus = $"Loaded {e.BytesReceived:N0} / {e.TotalBytesToReceive:N0} ({e.ProgressPercentage}%)";
DoEvents();// это тоже не помогает!
}
Вот картинка с закачками, здесь Count = число обращений к Web_DownloadProgressChanged и +1,000,000 если было обращение к Web_DownloadFileCompleted. Как видно, этот костыль WebClient даже не удосуживается везде вызывать финальный коллбэк (скачки со счётчиками 13, 11, 7)! При этом видно, что каждая закачка обращалась в среднем около десятка раз и НИ РАЗУ не отобразилась в UI! (прогресс забайнден на грид и у картинки дёргается PropertyChanged)
Если бы вообще ничего не работало, я б винил алгоритм/неумение работать с async, но ведь работает!! Но не сразу Есть какие-нибудь идеи?
Здравствуйте, Kolesiki, Вы писали:
K>1. Качается только ДВА задания, хотя работающих трэдов — несколько. Такое ощущение, что приложению не дают юзать больше 2 сокетов Кто-то слышал про подобные ограничения?
ХА-ХА! Не думал, что нагуглю этот маразм, взлелеянный MoronSoftom!! Да, оказывается ЕСТЬ ОГРАНИЧЕНИЕ! Кто бы мог подумать, что заботливые %идёт непереводимая игра слов с использованием местных идиоматических выражений% из мудософта заделают глобальный параметр ServicePointManager.DefaultConnectionLimit! И что мало нам гемороя с сокетами, ты ещё найди их неизвестный ServicePointManager и поколдуй над ним — нам же мало проблем с кодом!
Нет слов, хочется с ноги взять и.... научить студоту не лезть туда, где даже у дедушки не лез!
Здравствуйте, Kolesiki, Вы писали:
K>Нет слов, хочется с ноги взять и.... научить студоту не лезть туда, где даже у дедушки не лез!
Кэп: если писать код методом копипасты с stackoverflow, то да, приходится играть в д'Артаньяна и Микрософт.
А если для разнообразия изучить матчасть, то и с блокировкой UI-потока не надо извращаться, и решение стандартное находится на раз-два, да и без ругани всё как-то работает, сам удивляюсь
Другими словами: сначала разобраться, потом обвинять в рукожопости. Порядок важен.
The HttpClient object is intended to live for as long as your application needs to make HTTP requests. Having an object exist across multiple requests enables a place for setting DefaultRequestHeaders and prevents you from having to respecify things like CredentialCache and CookieContainer on every request, as was necessary with HttpWebRequest.
Здравствуйте, Sinix, Вы писали:
S>Кэп: если писать код методом копипасты с stackoverflow, то да, приходится играть в д'Артаньяна и Микрософт.
Если проектировать через жопу — то да, приходится играть в бога и вводить ***ер знает кому нужные ServicePointManager — кто додумался до этого маразма и кто его просил вообще вводить?
S>А если для разнообразия изучить матчасть, то и с блокировкой UI-потока не надо извращаться, и решение стандартное
А! Тогда понятно, сразу бы и сказал: сначала Микрософт "копирует со стэковерфлова" класс Semaphore, затем использует, задумчиво чешет репу (ну, ту, которой они как бы думают), а затем реализуют ЕЩЁ ОДИН ДУБЛИРУЮЩИЙ КЛАСС под названием SemaphoreSlim и пишут 10-страничную "матчасть" (которую вы предлагаете непонятно где брать — с того же стэковерфлова), и тогда всё работает!
За труды конечно спасибо — буду знать, что *Slim придуман неспроста (и он мне уже попадался в доках), но согласитесь, что это ГРАБЛИ, а не "матчасть".
Ну и чтоб 2 раза не вставать, решение пришлось делать хирургическое:
С постоянным рефрешем скачки работают и плавно обновляются. Оказывается, просто Semaphore.Wait(таймаут) уходит НАГЛУХО в себя, не удосуживая даже дать кусочек дотнетине. Как так? Если мы чего-то ЖДЁМ, то время ожидания можно как раз легко потратить на остальные трэды (в частности, ГУЁвый). У WPF наблюдаем глушняк.
Здравствуйте, Sinix, Вы писали:
S>Другими словами: сначала разобраться, потом обвинять в рукожопости. Порядок важен.
Рукожопость — это про ServicePointManager — согласись, что человек, который и так тратит кучу времени на разборки в МС классах, не должен ещё лезть в какие-то сервисные дебри только потому, что кто-то мелкомягкий умом решил поиграть в ограничения для юзеров! (кстати, это не первый факап — у XP где-то в регистри тоже была чуча, которая не давала использовать больше 20 сокетов — пришлось опять хачить!)
Я не поленился, переделал на SemaphoreSlim — за счёт await throttler.WaitAsync() всё заработало, спасибо ещё раз!
Здравствуйте, _Raz_, Вы писали:
_R_>In genral I would recommend reusing HttpClient instances as much as possible.
Ну да, станартная фишка для всех реализаций HttpClient, я тут особой проблемы не вижу
Если сравнивать с реализацией в яве — я про чехарду с ThreadSafeClientConnManager->PoolingClientConnectionManager->PoolingHttpClientConnectionManager — так вообще рай и тишина.
Здравствуйте, Kolesiki, Вы писали:
K>Я не поленился, переделал на SemaphoreSlim — за счёт await throttler.WaitAsync() всё заработало, спасибо ещё раз!
Парень, без обид, но ты сейчас выглядишь как первокурсник, на перезачёте узнавший что "всю эту хрень надо было ещё и учить?".
Вэлкам в реальную жизнь
Не нравится — можешь попробовать насладиться творческими решениями конкурентов. Очень рекомендую начать с полёта фантазии в яве.
Для просветления, такскть
Здравствуйте, Kolesiki, Вы писали:
K>Сам код — думаю, вы легко разберётесь, везде комменты.
K>
K> var web = new WebClient();// может, это WebClient такой уродский? Кто-то сталкивался с его некорректным поведением?
K> web.DownloadProgressChanged += Web_DownloadProgressChanged;// этот метод явно вызывается - проверено
K> web.DownloadFileCompleted += Web_DownloadFileCompleted;// этот тоже
K> web.DownloadFileTaskAsync(new Uri(pic), GeneratedName).Wait();// ждём завершения закачки и освобождаем семафор
K>
У WebClient есть по три версии методова загрузки:
1. DownloadFile — синхронный,
2. DownloadFileAsync — асинхронный, работает вместе с событиями DownloadProgressChanged, DownloadFileCompleted,
3. DownloadFileTaskAsync — асинхронный, основан на тасках.
В приведённом коде я вижу вызов метода возвращающего Task, при этом подписка на события другого метода...
Здравствуйте, Sinix, Вы писали:
S>Парень, без обид, но ты сейчас выглядишь как первокурсник, на перезачёте узнавший что "всю эту хрень надо было ещё и учить?".
Да какие обиды на агентов микрософта!
То, как я выгляжу в их глазах, никак не отменяет факта, что помимо класса "сокет" юзер вообще НИЧЕГО не обязан знать дополнительно — почитай любой туториал на Линукс — там ВСЁ, что нужно для работы с тырнетом — это сокет. Расскажи мне, я уже в ТРЕТИЙ раз прошу, кому и за каким хреном понадобился класс ServicePointManager, который за 10(!) лет программирования сетей в дотнете я вижу впервые? (и кому взбрело в голову ставить ГЛОБАЛЬНЫЕ ограничения там, где их не просили)
Короче, понимаешь, это не я студент — это ваша контора — студиозы (причём биологические), у которых (классическая психология подростков) просто чешется комплекс бога — всё запретить, ограничить и т.п. Я вас понимаю, но не оправдываю такое похабное проектирование.
Придумать сами себе сто классов и назвать это фрэймворком — это ещё не повод тыкать профессионалов "вы недостаточно покопались в нашем навозе" — запомни эту мысль.
Здравствуйте, koodeer, Вы писали:
K>> web.DownloadProgressChanged += Web_DownloadProgressChanged;// этот метод явно вызывается — проверено K>> web.DownloadFileCompleted += Web_DownloadFileCompleted;// этот тоже K>> web.DownloadFileTaskAsync(new Uri(pic), GeneratedName).Wait();// ждём завершения закачки и освобождаем семафор
K>У WebClient есть по три версии методова загрузки: K>1. DownloadFile — синхронный, K>2. DownloadFileAsync — асинхронный, работает вместе с событиями DownloadProgressChanged, DownloadFileCompleted, K>3. DownloadFileTaskAsync — асинхронный, основан на тасках.
K>В приведённом коде я вижу вызов метода возвращающего Task, при этом подписка на события другого метода...
Ха... MSDN и я читал! И самый прикол в том, что DownloadFileTaskAsync тоже работает! (да это и вполне логично — ожидать от асинхроны репортов прогресса)
А без тасков как мне ждать завершения-то?
Здравствуйте, Kolesiki, Вы писали:
K> помимо класса "сокет" юзер вообще НИЧЕГО не обязан знать дополнительно K> не повод тыкать профессионалов "вы недостаточно покопались в нашем навозе" — запомни эту мысль.
Здравствуйте, Kolesiki, Вы писали:
K>Ха... MSDN и я читал! И самый прикол в том, что DownloadFileTaskAsync тоже работает! (да это и вполне логично — ожидать от асинхроны репортов прогресса) K>А без тасков как мне ждать завершения-то?
Здравствуйте, Kolesiki, Вы писали:
K>То, как я выгляжу в их глазах, никак не отменяет факта, что помимо класса "сокет" юзер вообще НИЧЕГО не обязан знать дополнительно — почитай любой туториал на Линукс — там ВСЁ, что нужно для работы с тырнетом — это сокет.
Ну юзай сокеты, проблем то
K>Расскажи мне, я уже в ТРЕТИЙ раз прошу, кому и за каким хреном понадобился класс ServicePointManager,
Да тебе же и понадобился. Что, не догадаться в своей задаче установить ConnectionLimit = 5?
K> который за 10(!) лет программирования сетей в дотнете я вижу впервые? (и кому взбрело в голову ставить ГЛОБАЛЬНЫЕ ограничения там, где их не просили)
There are a whole bunch of other things that you can take advantage of in ServicePointManager – things such as the HttpConnection limit, which is based upon a W3 spec, but for internal back-end service calls over REST and the like, you may want to affect.
И ограничения не глобальные. Можно настроить хоть для каждого инстанса WebClient.
Здравствуйте, Kolesiki, Вы писали:
K>Ребят, помогите плиз с этой %#$#^ асинхронностью и WPF? K>Пишу такой велосипед, что удивляешься, почему он требует так много усилий!
Это мироздание посылает тебе сигнал: ты что-то делаешь не так! Надо остановиться и подумать, всегда помогает. K>Задача — "красивая" скачка файлов: есть список картинок, они ставятся в очередь и качаются, попутно отображая прогресс. K>Очередь ограничена 5 работающими трэдами (хотя и без Семафора те же проблемы). K>Как ДОЛЖНО работать: из 10 картинок 5 всегда должны отображать некий прогресс, пока все скачки не закончатся.
При чём тут "трэды"? Насколько я понял, тебе нужно, чтобы в любой момент времени выполнялось не более 5 заданий на скачку. И вообще, скачиванием занимается сетевое железо, ей "трэды" не нужны. Когда железо получит порцию данных, оно дёрнет процессор, который до этого момента вообще занимается своими делами.
async void Download_Clicked(object sender, RoutedEventArgs e)
{
var doneJobCount = 0;
// Пока не выполнили все заданияwhile (doneJobCount < PicsToLoad.Count)
{
// Берём очередных 5 заданийvar jobs = PicsToLoad.Skip(doneJobCount).Take(5);
var jobTasks =
// Для каждого заданияfrom job in jobs
// создаём качалку, подписываемся на события, запоминаем переданное задание...let webClient = CreateWebClient(job)
// запускаем скачиваниеselect webClient.DownloadFileTaskAsync(job.Url, job.FileName);
// Качаем и ждём завершения
await Task.WhenAll(jobTasks);
// Подсчитываем выполненные задания
doneJobCount += jobs.Count();
}
}
Здравствуйте, Vladek, Вы писали:
K>>Очередь ограничена 5 работающими трэдами (хотя и без Семафора те же проблемы). K>>Как ДОЛЖНО работать: из 10 картинок 5 всегда должны отображать некий прогресс, пока все скачки не закончатся.
V>При чём тут "трэды"?
Привык я к ним. Мне легче понимать "параллельные задачи" именно как явно ощущаемый кусок кода. А надеяться на мелкомягкие "пулы" — спасиб, один раз понадеялся — на ДВУХСОКЕТНЫЙ ПУЛ, блин позорище, а не фрэймворк.
V> Насколько я понял, тебе нужно, чтобы в любой момент времени выполнялось не более 5 заданий на скачку.
Понял — правильно, но вот реализовал по-моему криво.
V> // Берём очередных 5 заданий V> var jobs = PicsToLoad.Skip(doneJobCount).Take(5); V> // Качаем и ждём завершения V> await Task.WhenAll(jobTasks);
НЕТ! Мы не качаем "блоками по 5 файлов", а держим постоянную очередь из 5 (максимум) работяг. Если хотя бы один закончил работу, то на его место приходит новое задание на закачку. А у тебя ждёт всех 5-ти, а потом снова берёт 5.