Posts Dealing with reentrancy with DelegateCommand
Post
Cancel

Dealing with reentrancy with DelegateCommand

many times in our application we have some button (or another command invoker) that is executing a long processing operation.

today, with the power of Async Await its even easier to run these long operations while keeping the UI responsive. usually we will use some kind of DelegateCommand to bind our button to the ViewModels’ operation

MissleLauncherView.xaml

<Button  Command=”{Binding LaunchMissiles, Mode=OneTime}”/>

 

MissleLauncherViewModel.cs

private ICommand _launchMissilesCommand;public ICommand LaunchMissilesCommand

{

   get

   {

       return _ launchMissilesCommand ?? (_launchMissilesCommand = new DelegateCommand(           async (_) =>

           {

               await LaunchLongMissileAttack();

           }));
   }
}
 

this impose a very serious problem, while the LaunchLongMissileAttack method is working, nothing is preventing the command from being called againand againand again
in fact, we can tap repeatedly on the button (even by mistake).

of course, some application do need this ability, but many application don’t.
what we need here is some kind of reentrancy prevention, this is accomplished by using the
NonReenterantDelegateCommand.

Credit: i‘ve based my solution on my good friend Dror Helper post – http://blog.drorhelper.com/2014/02/callonce-for-c.html
you should definitly read it

public class NonReenterantDelegateCommand<T> : ICommand

{

    private const int NotCalled = 0;    private const int Called = 1;    private int _state = NotCalled;    readonly Func<T, Task> _execute;    Func<T, bool> _canExecute;    private bool _isExecuting = false;

 

    public NonReenterantDelegateCommand(Action<T> execute, Func<T, bool> canExecute = null)

    {

        if (execute == null)            throw new ArgumentNullException(“execute”);

        _execute = (_) =>
        {

            execute(_); return Task.FromResult(true);

        };
        SetCanExecute(canExecute);
    }
 

    private void SetCanExecute(Func<T, bool> canExecute)

    {
        _canExecute = canExecute;
    }
 

    public NonReenterantDelegateCommand(Func<T, Task> execute, Func<T, bool> canExecute = null)

    {

        if (execute == null)            throw new ArgumentNullException(“execute”);

        _execute = execute;
        _canExecute = canExecute;
    }
 

    public bool CanExecute(object parameter)

    {

        if (_isExecuting)

        {

            return false;

        }

        if (_canExecute == null)

        {

            return true;

        }

        return _canExecute((T)parameter);

    }
 

    public void RaiseCanExecuteChanged()

    {

        if (CanExecuteChanged != null)

        {

            Deployment.Current.Dispatcher.BeginInvoke(() => CanExecuteChanged(this, EventArgs.Empty));

        }
    }

    public event EventHandler CanExecuteChanged;    public async void Execute(object parameter)

    {

        var oldValue = Interlocked.Exchange(ref _state, Called);        if (oldValue == Called)

        {

            return;

        }

        _isExecuting = true;

        RaiseCanExecuteChanged();

        IDisposable finishExecutingDisposable = Disposable.Create(() =>

        {

            Interlocked.Exchange(ref _state, NotCalled);            _isExecuting = false;

            RaiseCanExecuteChanged();
        });
 

        using (finishExecutingDisposable)

        {

            await _execute((T)parameter);

        }
    }
}

lets explain what happens here. the magic takes place inside the Execute method, we use Interlocked on member field called _state, Interlocked.Exchange() method Sets a variable to a specified value as an atomic operation (meaning there will be no context switching) what makes it a thread-safe operation. the Exchange method returns the old value that was in the _state member.

if the old value was 1 (which is represented as the const Called) this means that there is already an execution taking place which means this is a reentrancy, so we just return.

if the old value is 0 (represented as the const NotCalled) this means we got here without any previous execution still going on. in this case we will set an internal flag that mark that there is an execution (this will be used in the CanExecute method) and raise the CanExecuteChanged event – this will cause the binded button to be disabled from this moment .
Then, We create a disposable that when disposed will revert the _state to NotCalled (thread safely) and raise the CanExecuteChanged event – this will cause the binded button to be enabled again.

finally we execute the the _execute delegate, the execution takes place inside a using block – this makes sure that the dispose will be called on the previously create disposable even if and exception happens (and prevent an infinitely locked button).

now we can change our ViewModel to use this new command.

MissleLauncherViewModel.cs

private ICommand _launchMissilesCommand;public ICommand LaunchMissilesCommand

{

   get

   {

       return _ launchMissilesCommand ?? (_launchMissilesCommand = new NonReenterantDelegateCommand<Object>(           async (_) =>

           {

               await LaunchLongMissileAttack();

           }));
   }
}
This post is licensed under CC BY 4.0 by Tamir Dresher.

Trending Tags