I'm using TPL dataflow in a WPF application following the MVVM pattern.
I have a TransformBlock<object,object>
and an ActionBlock<object>
and I'm linking them like so:
transformBlock.LinkTo(notificationBlock);
The ActionBlock<object>
should update the progress bar in my view with the current progress, but the UI seems to be frozen and only updates when everything finishes processing.
My CurrentProgress
property looks like this:
private double _CurrentProgress;
public double CurrentProgress
{
get { return _CurrentProgress; }
set
{
_CurrentProgress = value;
RaisePropertyChanged("CurrentProgress");
}
}
and I'm binding it to my View like so:
<ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}" Name="uxProgressBar"/>
Am I missing something? why is TPL blocking the UI thread?
EDIT
This is how I'm instantiating the TPL:
foreach(var myObj in ObjList)
{
transformBlock.Post(myObj);
}
Transform Block:
TransformBlock<object, object>(
temp =>
{
var response = ProcessRecord(temp);
return response.Status;
},
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism =20
});
Action Block:
ActionBlock<object>(
temp =>
{
CurrentProgress = (double)temp.RecordNumber/(double)TotalRecords;
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});
UPDATE
The web service being called in the TransformBlock
was a legacy (asmx) web service and it wasn't being called Async
. After fixing this issue everything else works fine without using the Dispatcher
or any of the other suggested solutions.
From one of the comments to the question it seems like WPF does support posting to the UI Thread from another Thread. I haven't been able to find any official documentation about this though.
First of all, your ActionBlock
don't have to change the CurrentProgress
property directly.
The reason is that the RaisePropertyChanged
function will directly run code of the ProgressBar
Object.
Which is not allowed as it's an Object owned by the UIThread.
The ActionBlock
run in his own thread, and he need to post the progressBar update order to the UI Thread (by using the Dispatcher.BeginInvoke
) :
ActionBlock<object>(
temp =>
{
double progress = (double)temp.RecordNumber/(double)TotalRecords;
Dispatcher.BeginInvoke((Action)(() =>
{
CurrentProgress = progress;
}));
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});
You'll have to change this first thing for sure in order to make what you want.
Secondly (if this does not change anything), you have to check that your UI Thread is not waiting for the completion of your TransformBlock
. If your ICommand
(if you're calling it from a button for exemple) is not ASync and do something like this :
transformBlock.Completion.Wait();
It's not ok. Because your UIThread will wait for the end of your treatment and will not take the previous progressBar update orders until the end.
Good luck ! Please post new details if it's alway not working.