WPF + WebClient + async = непонятная кривота
От: Kolesiki  
Дата: 08.11.15 16:20
Оценка: -1
Ребят, помогите плиз с этой %#$#^ асинхронностью и 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, но ведь работает!! Но не сразу Есть какие-нибудь идеи?
Re: WPF + WebClient + async = непонятная кривота
От: Kolesiki  
Дата: 08.11.15 18:25
Оценка:
Здравствуйте, Kolesiki, Вы писали:

K>1. Качается только ДВА задания, хотя работающих трэдов — несколько. Такое ощущение, что приложению не дают юзать больше 2 сокетов Кто-то слышал про подобные ограничения?


ХА-ХА! Не думал, что нагуглю этот маразм, взлелеянный MoronSoftom!! Да, оказывается ЕСТЬ ОГРАНИЧЕНИЕ! Кто бы мог подумать, что заботливые %идёт непереводимая игра слов с использованием местных идиоматических выражений% из мудософта заделают глобальный параметр ServicePointManager.DefaultConnectionLimit! И что мало нам гемороя с сокетами, ты ещё найди их неизвестный ServicePointManager и поколдуй над ним — нам же мало проблем с кодом!
Нет слов, хочется с ноги взять и.... научить студоту не лезть туда, где даже у дедушки не лез!
Re[2]: WPF + WebClient + async = непонятная кривота
От: Sinix  
Дата: 08.11.15 19:14
Оценка: 3 (1) +6
Здравствуйте, Kolesiki, Вы писали:

K>Нет слов, хочется с ноги взять и.... научить студоту не лезть туда, где даже у дедушки не лез!


Кэп: если писать код методом копипасты с stackoverflow, то да, приходится играть в д'Артаньяна и Микрософт.

А если для разнообразия изучить матчасть, то и с блокировкой UI-потока не надо извращаться, и решение стандартное находится на раз-два, да и без ругани всё как-то работает, сам удивляюсь
Другими словами: сначала разобраться, потом обвинять в рукожопости. Порядок важен.
Re: WPF + WebClient + async = непонятная кривота
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 08.11.15 19:56
Оценка:
Здравствуйте, Kolesiki, Вы писали:

Может ConfigureAwait(false) спасет и HTTPClient
и солнце б утром не вставало, когда бы не было меня
Re[3]: WPF + WebClient + async = непонятная кривота
От: _Raz_  
Дата: 08.11.15 23:44
Оценка:
Здравствуйте, Sinix, Вы писали:

S> решение стандартное находится


Там тоже не все гладко: HttpClient Lifecycle

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.


upd:
Henrik F. Nielsen (one of HttpClient's principal Architects): (via)

Everytime you create a new HttpClient you start a new session meaning that new TCP connections will have to get created etc.

In genral I would recommend reusing HttpClient instances as much as possible.

... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 78>>
Отредактировано 09.11.2015 2:00 _Raz_ . Предыдущая версия .
Re[3]: WPF + WebClient + async = непонятная кривота
От: Kolesiki  
Дата: 08.11.15 23:50
Оценка: -1
Здравствуйте, Sinix, Вы писали:

S>Кэп: если писать код методом копипасты с stackoverflow, то да, приходится играть в д'Артаньяна и Микрософт.


Если проектировать через жопу — то да, приходится играть в бога и вводить ***ер знает кому нужные ServicePointManager — кто додумался до этого маразма и кто его просил вообще вводить?

S>А если для разнообразия изучить матчасть, то и с блокировкой UI-потока не надо извращаться, и решение стандартное


А! Тогда понятно, сразу бы и сказал: сначала Микрософт "копирует со стэковерфлова" класс Semaphore, затем использует, задумчиво чешет репу (ну, ту, которой они как бы думают), а затем реализуют ЕЩЁ ОДИН ДУБЛИРУЮЩИЙ КЛАСС под названием SemaphoreSlim и пишут 10-страничную "матчасть" (которую вы предлагаете непонятно где брать — с того же стэковерфлова), и тогда всё работает!

За труды конечно спасибо — буду знать, что *Slim придуман неспроста (и он мне уже попадался в доках), но согласитесь, что это ГРАБЛИ, а не "матчасть".

Ну и чтоб 2 раза не вставать, решение пришлось делать хирургическое:

[c#]
public void RefreshGrid()
{
Dispatcher.Invoke(DispatcherPriority.Render, (Action)(() => { MyGridWithDownloads.Items.Refresh(); }));
}
[c#]

С постоянным рефрешем скачки работают и плавно обновляются. Оказывается, просто Semaphore.Wait(таймаут) уходит НАГЛУХО в себя, не удосуживая даже дать кусочек дотнетине. Как так? Если мы чего-то ЖДЁМ, то время ожидания можно как раз легко потратить на остальные трэды (в частности, ГУЁвый). У WPF наблюдаем глушняк.
Re[3]: WPF + WebClient + async = непонятная кривота
От: Kolesiki  
Дата: 09.11.15 00:01
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Другими словами: сначала разобраться, потом обвинять в рукожопости. Порядок важен.


Рукожопость — это про ServicePointManager — согласись, что человек, который и так тратит кучу времени на разборки в МС классах, не должен ещё лезть в какие-то сервисные дебри только потому, что кто-то мелкомягкий умом решил поиграть в ограничения для юзеров! (кстати, это не первый факап — у XP где-то в регистри тоже была чуча, которая не давала использовать больше 20 сокетов — пришлось опять хачить!)

Я не поленился, переделал на SemaphoreSlim — за счёт await throttler.WaitAsync() всё заработало, спасибо ещё раз!
Re[4]: WPF + WebClient + async = непонятная кривота
От: _Raz_  
Дата: 09.11.15 01:46
Оценка:
Здравствуйте, Kolesiki, Вы писали:

K>Рукожопость — это про ServicePointManager


Ну и использовал бы эту рукожопость себе на благо
... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 78>>
Re[4]: WPF + WebClient + async = непонятная кривота
От: Sinix  
Дата: 09.11.15 05:43
Оценка: 2 (1)
Здравствуйте, _Raz_, Вы писали:

_R_>In genral I would recommend reusing HttpClient instances as much as possible.

Ну да, станартная фишка для всех реализаций HttpClient, я тут особой проблемы не вижу

Если сравнивать с реализацией в яве — я про чехарду с ThreadSafeClientConnManager->PoolingClientConnectionManager->PoolingHttpClientConnectionManager — так вообще рай и тишина.
Re[4]: WPF + WebClient + async = непонятная кривота
От: Sinix  
Дата: 09.11.15 05:56
Оценка: 10 (1) +3
Здравствуйте, Kolesiki, Вы писали:

K>Я не поленился, переделал на SemaphoreSlim — за счёт await throttler.WaitAsync() всё заработало, спасибо ещё раз!


Парень, без обид, но ты сейчас выглядишь как первокурсник, на перезачёте узнавший что "всю эту хрень надо было ещё и учить?".

Вэлкам в реальную жизнь
Не нравится — можешь попробовать насладиться творческими решениями конкурентов. Очень рекомендую начать с полёта фантазии в яве.
Для просветления, такскть
Re: WPF + WebClient + async = непонятная кривота
От: koodeer  
Дата: 09.11.15 10:56
Оценка: +1
Здравствуйте, 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, при этом подписка на события другого метода...
Re[5]: WPF + WebClient + async = непонятная кривота
От: Kolesiki  
Дата: 09.11.15 12:07
Оценка: -7 :)))
Здравствуйте, Sinix, Вы писали:

S>Парень, без обид, но ты сейчас выглядишь как первокурсник, на перезачёте узнавший что "всю эту хрень надо было ещё и учить?".


Да какие обиды на агентов микрософта!
То, как я выгляжу в их глазах, никак не отменяет факта, что помимо класса "сокет" юзер вообще НИЧЕГО не обязан знать дополнительно — почитай любой туториал на Линукс — там ВСЁ, что нужно для работы с тырнетом — это сокет. Расскажи мне, я уже в ТРЕТИЙ раз прошу, кому и за каким хреном понадобился класс ServicePointManager, который за 10(!) лет программирования сетей в дотнете я вижу впервые? (и кому взбрело в голову ставить ГЛОБАЛЬНЫЕ ограничения там, где их не просили)
Короче, понимаешь, это не я студент — это ваша контора — студиозы (причём биологические), у которых (классическая психология подростков) просто чешется комплекс бога — всё запретить, ограничить и т.п. Я вас понимаю, но не оправдываю такое похабное проектирование.
Придумать сами себе сто классов и назвать это фрэймворком — это ещё не повод тыкать профессионалов "вы недостаточно покопались в нашем навозе" — запомни эту мысль.
Re[2]: WPF + WebClient + async = непонятная кривота
От: Kolesiki  
Дата: 09.11.15 12:09
Оценка:
Здравствуйте, 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 тоже работает! (да это и вполне логично — ожидать от асинхроны репортов прогресса)
А без тасков как мне ждать завершения-то?
Re[6]: WPF + WebClient + async = непонятная кривота
От: Sinix  
Дата: 09.11.15 12:39
Оценка: +3 -1
Здравствуйте, Kolesiki, Вы писали:

K> помимо класса "сокет" юзер вообще НИЧЕГО не обязан знать дополнительно

K> не повод тыкать профессионалов "вы недостаточно покопались в нашем навозе" — запомни эту мысль.

Свалил из спора, безнадёжно
Re[3]: WPF + WebClient + async = непонятная кривота
От: Klikujiskaaan КНДР  
Дата: 09.11.15 12:49
Оценка: +1
Здравствуйте, Kolesiki, Вы писали:

K>Ха... MSDN и я читал! И самый прикол в том, что DownloadFileTaskAsync тоже работает! (да это и вполне логично — ожидать от асинхроны репортов прогресса)

K>А без тасков как мне ждать завершения-то?

Выучить наконец TPL.
Re[4]: WPF + WebClient + async = непонятная кривота
От: Sinix  
Дата: 09.11.15 12:57
Оценка: +3
Здравствуйте, Klikujiskaaan, Вы писали:

K>Выучить наконец TPL.


Не тролльте человека. Он не хочет TPL. Он хочет socket и в linux.
Re[6]: WPF + WebClient + async = непонятная кривота
От: _Raz_  
Дата: 09.11.15 14:04
Оценка:
Здравствуйте, 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.
... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 78>>
Re[5]: WPF + WebClient + async = непонятная кривота
От: Vladek Россия Github
Дата: 10.11.15 18:54
Оценка: :))) :))
Здравствуйте, Sinix, Вы писали:

S>Не тролльте человека. Он не хочет TPL. Он хочет socket и в linux.


Завернуться в тёплый клетчатый плед, забраться в кресло с ногами, взять чашку какао, клавиатуру — и программировать сокеты с семафорами! Романтика!
Re: WPF + WebClient + async = непонятная кривота
От: Vladek Россия Github
Дата: 10.11.15 19:37
Оценка: +1
Здравствуйте, 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();
    }
}
Re[2]: WPF + WebClient + async = непонятная кривота
От: Kolesiki  
Дата: 04.07.16 23:40
Оценка:
Здравствуйте, 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.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.