2

I'm developing an asynchronous application using WPF and MVVM, but I can't seem to get an async method to run inside my relaycommand.

I have a button on my WPF view hooked up to a relaycommand in my viewmodel, which trys to call an async method in my model to return a list of results:

/// <summary>
/// Search results 
/// </summary>
private ObservableCollection<string> _searchResults = new ObservableCollection<string>(); 
public IList<string> SearchResults
{
    get { return _searchResults; }
}

/// <summary>
/// Search button command
/// </summary>
private ICommand _searchCommand;
public ICommand SearchCommand
{
    get
    {
        _searchCommand = new RelayCommand(
            async() =>
            {
                SearchResults.Clear();
                var results = await DockFindModel.SearchAsync(_selectedSearchableLayer, _searchString);
                foreach (var item in results)
                {
                    SearchResults.Add(item);
                }                    
                //notify results have changed
                NotifyPropertyChanged(() => SearchResults);
            },
            () => bAppRunning); //command will only execute if app is running
        return _searchCommand;
    }
}

However I get the following exception when the relaycommand tries to execute:

An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll

Additional information: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.

I've tried a number of things in this thread to try and resolve the issue with no luck. Does anyone know how to resolve this?

Community
  • 1
  • 1
pvdev
  • 230
  • 3
  • 13
  • 2
    What is the inner exception? – Yuval Itzchakov Jul 23 '15 at 09:47
  • What does your RelayCommand looks like? – Tomtom Jul 23 '15 at 09:48
  • Fixed it! Thanks anyway folks. – pvdev Jul 23 '15 at 09:53
  • 1
    Fixed it or dodged it? – H H Jul 23 '15 at 09:55
  • I know I'm waking up an old thread here but shouldn't you be awaiting the lambda function? I would have tried changing `async() =>` to `async() => await`. – Mark Feldman Nov 07 '16 at 02:31
  • For anyone coming here and not satisfied with accepted answer, go to [https://reactiveui.net/](https://reactiveui.net/) and take a look at ReactiveCommand - it makes working with async commands a blast – Krzysztof Skowronek Jul 01 '19 at 09:38
  • This is from forever ago, so I won't dig too deep, but the first problem I see is that the getter returns a new `RelayCommand` every time it's accessed. That's never what you want. Instead, checking `_searchCommand` for null and only assigning it then (lazy initialization), or just initialize it in your constructor. – Jamie Sep 06 '22 at 00:34

3 Answers3

7

Not sure where your RelayCommand is coming from (MVVM framework or custom implementation) but consider using an async version.

public class AsyncRelayCommand : ICommand
{
    private readonly Func<object, Task> execute;
    private readonly Func<object, bool> canExecute;

    private long isExecuting;

    public AsyncRelayCommand(Func<object, Task> execute, Func<object, bool> canExecute = null)
    {
        this.execute = execute;
        this.canExecute = canExecute ?? (o => true);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void RaiseCanExecuteChanged()
    {
        CommandManager.InvalidateRequerySuggested();
    }

    public bool CanExecute(object parameter)
    {
        if (Interlocked.Read(ref isExecuting) != 0)
            return false;

        return canExecute(parameter);
    }

    public async void Execute(object parameter)
    {
        Interlocked.Exchange(ref isExecuting, 1);
        RaiseCanExecuteChanged();

        try
        {
            await execute(parameter);
        }
        finally
        {
            Interlocked.Exchange(ref isExecuting, 0);
            RaiseCanExecuteChanged();
        }
    }
}
Cameron MacFarland
  • 70,676
  • 20
  • 104
  • 133
  • If there is an exception in `execute`, the visual studio debugger will highlight the `await execute(parameter);` line. I was expecting the debugger to highlight the actual erroneous line inside the `execute` delegate. Can we somehow manage this? – Athafoud Jun 15 '16 at 08:52
0

Ok so I actually managed to resolve the issue by making a change to the way I run my async method.

I changed this:

var results = await DockFindModel.SearchAsync();

To this:

var results = await QueuedTask.Run(() => DockFindModel.SearchAsync());

Although i'm a bit confused as to why I need to await Task.Run() when relaycommand is already accepting async lambda. I'm sure it'll become clear with time however.

Thanks to those who commented.

pvdev
  • 230
  • 3
  • 13
0

That RelayCommand class is probably accepting a parameter of type Action. Since you are passing an async lambda, we are having an async void scenario here. Your code will be fired and forgot. Consider using an ICommand implementation that takes Func<Task> as a parameter instead of Action.

Tom
  • 489
  • 4
  • 10