Индикатор выполнения в потоке данных WPF MVVM и TPL

Я использую поток данных TPL в приложении WPF по шаблону MVVM. У меня есть TransformBlock<object,object> и ActionBlock<object>, и я связываю их так:

transformBlock.LinkTo(notificationBlock);

ActionBlock<object> должен обновить индикатор выполнения в моем представлении с текущим прогрессом, но пользовательский интерфейс, кажется, заморожен и обновляется только после того, как все завершит обработку.

Мое свойство CurrentProgress выглядит так:

private double _CurrentProgress;

public double CurrentProgress
{
    get { return _CurrentProgress; }
    set
    {
        _CurrentProgress = value;
        RaisePropertyChanged("CurrentProgress");
    }
}

и я привязываю его к моему представлению так:

<ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}" Name="uxProgressBar"/>

Я что-то упускаю? почему TPL блокирует поток пользовательского интерфейса?

ИЗМЕНИТЬ

Вот как я создаю экземпляр TPL:

foreach(var myObj in ObjList)
{
    transformBlock.Post(myObj);
}

Блок преобразования:

TransformBlock<object, object>(
temp =>
{
    var response = ProcessRecord(temp);
    return response.Status;
},
new ExecutionDataflowBlockOptions
{
    MaxDegreeOfParallelism =20
});

Блок действий:

ActionBlock<object>(
temp =>
{
    CurrentProgress = (double)temp.RecordNumber/(double)TotalRecords;
},
new ExecutionDataflowBlockOptions
{
    TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

ОБНОВЛЕНИЕ

Веб-служба, вызываемая в TransformBlock, была устаревшей (asmx) веб-службой и не называлась Async. После устранения этой проблемы все остальное работает нормально без использования Dispatcher или любого другого предлагаемого решения.

Из одного из комментариев к вопросу кажется, что WPF поддерживает публикацию в поток пользовательского интерфейса из другого потока. Однако я не смог найти никакой официальной документации по этому поводу.


person SOfanatic    schedule 09.04.2014    source источник
comment
Как вы создаете экземпляр TPL?   -  person 123 456 789 0    schedule 09.04.2014
comment
Разместите соответствующий код, вы, вероятно, не «ждете» своего кода должным образом.   -  person aybe    schedule 09.04.2014
comment
Мне нравится, когда все работает в примерах проектов. У меня нет примера TPL, но посмотрите этот проект. Заставьте TPL работать в этом примере проекта, а затем перейдите к рабочему проекту. wpfsharp.com/2010/12/29/   -  person Rhyous    schedule 10.04.2014
comment
Вы Wait() занимаетесь Completion блоками или что-то в этом роде? Сколько элементов и сколько времени занимает их обработка?   -  person svick    schedule 10.04.2014
comment
@svick Я новичок в потоке данных TPL. Код, который я разместил, это практически весь код, Wait() нигде больше ничего нет.   -  person SOfanatic    schedule 10.04.2014
comment
Это должно работать, вам даже не нужен планировщик задач (WPF может обрабатывать обновления связанных свойств из других потоков). Возможно, есть какая-то ошибка привязки? Запустите в режиме отладки и проверьте вывод на наличие System.Windows.Data ошибок.   -  person Eli Arbel    schedule 17.04.2014
comment
@EliArbel Думаю, ты прав, проблема была в другом. Вызываемая веб-служба была устаревшей веб-службой (asmx), и она использовалась синхронно, а не асинхронно, поэтому поток пользовательского интерфейса блокировался.   -  person SOfanatic    schedule 18.04.2014


Ответы (3)


Во-первых, вашему ActionBlock не нужно напрямую изменять свойство CurrentProgress.

Причина в том, что функция RaisePropertyChanged будет напрямую запускать код объекта ProgressBar. Что не разрешено, поскольку это объект, принадлежащий UIThread.

ActionBlock запускается в своем собственном потоке, и ему нужно опубликовать порядок обновления progressBar в потоке пользовательского интерфейса (с помощью Dispatcher.BeginInvoke):

ActionBlock<object>(
temp =>
{
    double progress = (double)temp.RecordNumber/(double)TotalRecords;
    Dispatcher.BeginInvoke((Action)(() =>
    {
        CurrentProgress = progress;
    }));
},
new ExecutionDataflowBlockOptions
{
    TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

Вам придется изменить это в первую очередь, чтобы сделать то, что вы хотите.

Во-вторых (если это ничего не меняет), вы должны проверить, что ваш поток пользовательского интерфейса не ожидает завершения вашего TransformBlock. Если ваш ICommand (например, если вы вызываете его с помощью кнопки) не является ASync и сделайте что-то вроде этого:

transformBlock.Completion.Wait();

Это не хорошо. Потому что ваш UIThread будет ждать окончания вашего лечения и не будет принимать предыдущие заказы на обновление progressBar до конца.

Удачи ! Пожалуйста, опубликуйте новые подробности, если это всегда не работает.

person Gerard Walace    schedule 19.04.2014
comment
Не вызовет ли доступ к CurrentProgress из фонового потока исключение InvalidOperationException? он должен был это видеть, или, может быть, ActionBlock проглатывает исключение? - person Yuval Itzchakov; 20.04.2014
comment
Как вы сказали, он проглочен. Вы видите его только в том случае, если вы отлаживаете приложение (с установленным флажком Debug -> Exception -> catch exceptions) или если вы ждете ActionBlock.Completion. Ждать(). - person Gerard Walace; 20.04.2014
comment
Это обычное поведение для любого TransformationBlock? - person Yuval Itzchakov; 20.04.2014
comment
Созданное исключение повлияет на вашу сеть TPL (по крайней мере, оно убьет ваш поток ActionBlock), но ваш основной поток (в данном случае UIThread) не будет уведомлен напрямую, если только вы не создадите зависимость между ним и вашим TPL. Потоки (например, ожидая одного из них). Если вас интересует эта конкретная тема, вы можете найти полезную информацию здесь: Обработка исключений (библиотека параллельных задач) - person Gerard Walace; 20.04.2014
comment
Я знаю о способе распространения исключений TPL, мне было интересно, ведет ли Dataflow то же самое :) - person Yuval Itzchakov; 20.04.2014
comment
Насколько я знаю да. Нас просветил бы более опытный в этом вопросе парень :) Было бы интересно. - person Gerard Walace; 20.04.2014
comment
Здесь представлены простые примеры потока данных TPL. Я только что запустил один из них без блока try/catch и подтвердил, что основной поток не знает об исключении. На стороне ActionBlock .Completion.IsFaulted становится истинным, и он больше не обрабатывает ввод. Это все. - person Gerard Walace; 21.04.2014
comment
Хороший! Спасибо за эту информацию, возможно, вам следует добавить ее в свой ответ. - person Yuval Itzchakov; 21.04.2014

Попробуйте опубликовать свой код в TransformBlock с помощью SendAsync:

foreach (var myObj in ObjList)
{
   await transformBlock.SendAsync(myObj);
}
person Yuval Itzchakov    schedule 17.04.2014

Вы должны использовать Dispatcher, чтобы уведомить поток пользовательского интерфейса о том, что вы хотите обновить связанное значение из фонового процесса.

Вот ссылка на статью с объяснением:

http://msdn.microsoft.com/en-us/magazine/cc163328.aspx

http://msdn.microsoft.com/en-us/library/vstudio/system.windows.threading.dispatcher

Я соберу аналогичный пример кода и вскоре обновлю этот ответ.

person Glenn Ferrie    schedule 18.04.2014