views:

407

answers:

3

In my .NET application built with WPF and C# I call an async function using AsyncMethodCaller. In the callback I'd like to update some data in the GUI, but I'm not allowed to as this is owned by the main thread. How to I do it?

  • Invoke an update on the main thread? How?
  • Pass an object (e.g. ViewModel) as state to the callback and update data on this - which again is bound to the GUI?
  • Some other way?

What's the common, recommended way of handling this?

The runtime error given is:

The calling thread cannot access this object because a different thread owns it.

+2  A: 

You need to invoke the method using the Dispatcher, calling Dispatcher.Invoke method. This MSDN article explains how to update UI in WPF from asynchronous operations in great detail.

Marek
A: 

I tried passing the ViewModel object as asyncState such that the callback can access this object. The callback will typically update some property with a value received from the async function call. The ViewModel will eventually be where I want to do the status update anyway. Is this a proper way of handling it? Or should I always use Dispatcher.Invoke?

The ViewModel:

public class MyViewModel : BaseViewModel
{
    public int Result
    {
        get { return _result; }
        set
        {
            _result = value; 
            this.RaisePropertyChanged("Result");
        }
    }
}

Calling the function:

caller.BeginInvoke(num1, num2, new AsyncCallback(CallbackMethod), _myViewModel);

The callback updates the viewmodel:

private void CallbackMethod(IAsyncResult ar)
{
    var result = (AsyncResult)ar;
    var caller = (AsyncMethodCaller)result.AsyncDelegate;
    var vm = ar.AsyncState as MyViewModel;
    int returnValue = caller.EndInvoke(ar);
    vm.Result = returnValue; 
}
stiank81
+2  A: 

Bambuska,

Using Dispatcher is really a lot easier than you think. Please take a look at my code:

public class MyViewModel : BaseViewModel    
{    
    public int Result    
    {    
        get { return _result; }    
        set    
        {    
            _result = value;     

      Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new WorkMethod(delegate
       {
        this._result = SampleMethodChangingResult();
       }));

            this.RaisePropertyChanged("Result");    
        }    
    }    
}

This should work (it does in my case). Anyway, please keep me informed.

Piotr Justyna
Thanks! The principle you show here helps me, though the code doesn't seem to work "out of the box". And would you really do the invoking from the viewmodel? That would mean you have to pass the viewModel object as state anyway as you can't access this either if belonging to the main thread? So I assume you'd do the invoke directly from the callback?
stiank81
Here's what I do from the callback. Note that I can't use Dispatcher.CurrentDispatcher.Invoke as I need the one belonging to the main thread - I assume..? Application.Current.Dispatcher.Invoke (DispatcherPriority.Normal,(ThreadStart)(() => UpdateSomething(returnValue)));
stiank81
I guess you can do that, but why do you insist on passing the vm object? You definitely can execute my code within vm object and since vm is bound to a view object as a DataContext it is for sure executed in the main thread.
Piotr Justyna
I don't insist on passing the vm object. I'm saying you must have relied on this since you do the invoking in the viewmodel. In my example above I don't rely on receiving the vm object through asyncState. Busy with something else at the moment, but will get back to this soon.. Thx for following up.
stiank81