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.
Try posting your code to the TransformBlock using 'SendAsync':
foreach (var myObj in ObjList)
{
await transformBlock.SendAsync(myObj);
}
You should use the Dispatcher
to notify the UI thread that you want to update a bound value from a background process.
Here is a link to an article explaining:
http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
http://msdn.microsoft.com/en-us/library/vstudio/system.windows.threading.dispatcher
I will put together a similar code example and update this response shortly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With