2

I am experiencing some confusion with Tasks and the async/await key words. I understand that you should NOT mix async and blocking code. Or at least what my interpretation of mixing them is:

Don't make calls to blocking API's from non- async methods. So here's my issue.

I am trying to await a method, then update the UI accordingly. The issue is that the only way to await an async method() call is from within and async method().

Here's an example:

private RelayCommand<Options> _executeCommand;

public RelayCommand<Options> ExecuteCommand
{
    get
    {                
        return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
        {
            Completed = false;                    

            var cancellationTokenSource = new CancellationTokenSource();

            await RunValidation(options, cancellationTokenSource.Token);

            Completed = true;

        }));
    }
}

This code runs the method properly and awaits. The issue is when I return. For some reason when setting the Complete flag the buttons dependent on this flag are not toggled. If I comment the await code, then the buttons are toggled correctly. So assumed I was not returning on the UI thread, so I tried using this code instead:

private RelayCommand<Options> _executeCommand;

public RelayCommand<Options> ExecuteCommand
{
    get
    {                
        return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
        {
            Completed = false;                    

            var cancellationTokenSource = new CancellationTokenSource();                                                                

            var context = TaskScheduler.FromCurrentSynchronizationContext();

            await RunValidation(options, cancellationTokenSource.Token).ContinueWith(t => Completed = true, context);

            //Completed = true;

        }));
    }
}

Here is the RunValidation() method:

private async Task RunValidation(Options options, CancellationToken token)
{            
    await _someService.SomAsyncMethod(options, token));
}

If you notice, the ExecuteCommand has an async key word before the (options) parameter that is passed to the command. If I remove the async key word then I have to modify the call to the RunValidation() method. I still need it to await, so this is what I did:

private RelayCommand<Options> _executeCommand;

public RelayCommand<Options> ExecuteCommand
{
    get
    {                
        return _executeCommand ?? (_executeCommand = new RelayCommand<Options>((options) =>
        {
            Completed = false;                    

            var context = TaskScheduler.FromCurrentSynchronizationContext();

            var cancellationTokenSource = new CancellationTokenSource();

            Task.Run(async () => await RunValidation(options, cancellationTokenSource.Token));

            Completed = true;

        }));
    }
}

The problem with this code is that it doesn't await. So I am at a loss.

Can anyone shed some light on this for me please. I've spend 2 plus days on this and I am still here.

Thanks, Tim

Here are the bindings to the Command Buttons.

private readonly Independent<bool> _completed = new Independent<bool>(true);

public bool Completed
{
    get { return _completed; }
    set { _completed.Value = value; }
}

private ICommand _doneCommand;

public ICommand DoneCommand
{
    get
    {
        return _doneCommand ?? (_doneCommand = MakeCommand.When(() => Completed).Do(() =>
        {
            DoSomething();
        }));
    }
}

private ICommand _cancelCommand;

public ICommand CancelCommand
{
    get
    {
        return _cancelCommand ??
               (_cancelCommand = MakeCommand.When(() => !Completed).Do(() => DoSomthingElse()));
    }
}

I am using the MakeCommand objects from the UpdateControls library from Michael Perry. They contain dependancy tracking that raises the CanExecuteChange events when the Complete property is changed.

5
  • 2
    The first block of code is correct. How are the buttons bound to the Completed flag? Is it observable? Commented Apr 13, 2017 at 21:41
  • 1
    Since your problem is due to notifications being lost, and since you're using a library that should be raising your notifications for you, I recommend that you raise an issue with the UpdateControls library. Commented Apr 13, 2017 at 21:54
  • So I am not crazy and from the looks of things, I am doing things correctly? Commented Apr 13, 2017 at 21:57
  • Have you set IsAsync=true in binding of ExecuteCommand in XAML? Commented Apr 13, 2017 at 22:01
  • 1
    I'm not familiar with that library so maybe there is something more going on but with normal MVVM you would need to fire the NotifyPropertyChanged event when setting the Complete property so that anything binding to it will be notified. That's just a suggestion and may not be needed depending on the inner workings of how this is setup. Possibly add that and see if it changes things? Commented Apr 14, 2017 at 1:57

2 Answers 2

0

Your first code is correct. Most likely you have an incorrect implementation for your Completed property. Your view model object should implement INotifyPropertyChanged. The easiest way to do this right is use a base class that provides the functionality. ReactiveUI is the nuget package I always use. Usage is as simple as

public class MyObject : ReactiveObject {

    private bool _Completed;
    public bool Completed {
        get => _Completed;
        set => this.RaiseAndSetIfChanged(ref _Completed, value);
    }

}

This will make sure that notifications are raised to the UI when the property is changed.

If you want it to be more magic you can use ReactiveUI.Fody and then your code will reduce to

public class MyObject : ReactiveObject {

    [Reactive]
    public bool Completed { get; set;}

}
Sign up to request clarification or add additional context in comments.

Comments

0

So the issue was in fact the third party library. I was using it to provide dependency tracking for the 2 buttons. So when the complete flag changed it raised the CanExecuteChange events for both buttons without me having write code to do it. Unfortunately it stopped working after introducing the async/await calls. I replaced the 2 MakeCommands with RelayCommands and raised the events myself and everything worked.

So thanks to everyone for your responses.

Tim

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.