Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top