سؤال

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;
}
هل كانت مفيدة؟

المحلول 2

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.

نصائح أخرى

You need to update your DepedencyProperty via the UI Thread. Use:

Application.Current.Dispatcher.BeginInvoke(Action)

Or:

Application.Current.Dispatcher.Invoke(Action)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top