Reputation: 29710
I am trying to set a dependency property which is updated by a WCF callback thread.
There is a ProgressBar on MainWindow.xaml that is bound to this property:
MainWindow.xaml
<ProgressBar Name="ProgressBar" Value="{Binding Progress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
MainWindow has an instance of DemoModule, which is defined as:
DemoModule.xaml.cs
/// <summary>
/// Interaction logic for DemoModule.xaml
/// </summary>
public partial class DemoModule : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", typeof(int), typeof(DemoModule));
public event ProgressEventHandler ProgressChanged;
public event PropertyChangedEventHandler PropertyChanged;
public int Progress
{
get { return (int)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); } // setter throws InvalidOperationException "The calling thread cannot access this object because a different thread owns it"
}
/// <summary>
/// Initializes a new instance of the <see cref="DemoModule" /> class.
/// </summary>
public DemoModule()
{
InitializeComponent();
ProgressChanged += OnProgressChanged;
}
/// <summary>
/// Called when [progress changed].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="args">The <see cref="ProgressChangedEventArgs" /> instance containing the event data.</param>
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Debug.WriteLine("Current Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("Current Dispatcher Thread: {0}", Application.Current.Dispatcher.Thread.ManagedThreadId);
if (ProgressChanged == null) return;
Debug.WriteLine("ProgressChangedEventArgs.Current: " + args.Current);
Progress = Convert.ToInt32(args.Current * 100);
OnPropertyChanged("Progress");
}
/// <summary>
/// Called when [property changed].
/// </summary>
/// <param name="propertyName">Name of the property.</param>
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
Trace.WriteLine("Property " + propertyName + " changed. Value = " + Progress);
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The Progress.set()
is throwing an exception because of the thread affinity.
How can I fix this?
Update 1
This is allegedly thread safe, but has no effect:
public int Progress
{
get
{
return Dispatcher.Invoke((() => (int)GetValue(ProgressProperty)));
}
set
{
Dispatcher.BeginInvoke((Action)(() => SetValue(ProgressProperty, value)));
}
}
Update 2
My DemoModule.xaml.cs has a reference to a client library which implements the WCF callback method OnUpdateProgress:
InstallerAgentServiceClient.cs
public void OnUpdateProgress(double progress)
{
//Debug.WriteLine("Progress: " + progress*100 + "%");
var args = new ProgressChangedEventArgs(progress, 1, "Installing");
_installModule.OnProgressChanged(this, args);
}
The _installModule
object above is the instance of DemoModule
.
Update 3
After removing the [CallBackBehavior]
attribute from the WCF client library, there no longer seems to be thread synchronization issues. I can update the progress bar in the MainWindow as follows:
DemoModule.xaml.cs
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Progress = Convert.ToInt32(args.Current * 100);
var progressBar = Application.Current.MainWindow.FindName("ProgressBar") as ProgressBar;
if (progressBar != null)
progressBar.Value = Progress;
}
Upvotes: 3
Views: 3581
Reputation: 726
I recommand using the IProgress interface. Works like a charm for me and is pretty easy to use. In your progressbarVM add
public double Actualprogress
{
get { return (double)GetValue(ActualprogressProperty); }
set { SetValue(ActualprogressProperty, value); }
}
public static readonly DependencyProperty ActualprogressProperty =
DependencyProperty.Register("Actualprogress", typeof(double), typeof(ProgressBar),
new PropertyMetadata(0.0));
then call your method as an asyn task using await like so :
var progress = new Progress<double>(progressPercent =>
progressBarVM.progressBar.Actualprogress = progressPercent);
Parser parser = new Parser();
ModelVMResult result = await Task.Run(() => parser.Parse(filename,progress));
then in your method "parse" just do:
float threshold = 0.0f;
for (int i = 0; i < count; i++)
{
if (i >= threshold)
{ progress.Report(prog += 1); threshold += count / 100.0f; }
this.Readline(reader, i);
}
Of course you need to bind your xaml progressbar.Value to ProgressbarVM.Actualprogress. Then your progressbar will update and your app will still be responsive during the process.
Upvotes: 0
Reputation: 149538
You need to update your DepedencyProperty via the UI Thread. Use:
Application.Current.Dispatcher.BeginInvoke(Action)
Or:
Application.Current.Dispatcher.Invoke(Action)
Upvotes: 6