Search code examples
asynchronousxamarincommandmvvmcross

MvvmCross Async command lock


I have alot of button in my application. They are placed next to each other. All of the methods are IMvxAsyncCommand type. I figured out some missmatches after tests done by users. I have found duplicate operations - two diffrent buttons are called in almost same time.

What did I do is created my own SafeAsyncCommand class and inheret from MvxAsyncCommand. My goal is to create delay between executes - I want to prevent double click in given delay in below case 0.5s.

There is my work:

public static class SafeCommandSettings
    {
        public static bool CanExecute { get; private set; }
        public static TimeSpan Delay => TimeSpan.FromMilliseconds(500);

        static SafeCommandSettings()
        {
            CanExecute = true;
        }

        public static async void Pause()
        {
            if (!CanExecute) return;

            CanExecute = false;
            await Task.Delay(Delay);
            CanExecute = true;
        }
    }

    public class SafeAsyncCommand : MvxAsyncCommand
    {
        public SafeAsyncCommand(Func<Task> execute, Func<bool> canExecute = null, bool allowConcurrentExecutions = false)
            : base(execute, canExecute, allowConcurrentExecutions)
        {
        }

        public SafeAsyncCommand(Func<CancellationToken, Task> execute, Func<bool> canExecute = null, bool allowConcurrentExecutions = false)
            : base(execute, canExecute, allowConcurrentExecutions)
        {
        }

        protected override async Task ExecuteAsyncImpl(object parameter)
        {
            if (!SafeCommandSettings.CanExecute) return;

            SafeCommandSettings.Pause();
            await base.ExecuteAsyncImpl(parameter);
        }
    }

    public class SafeAsyncCommand<T> : MvxAsyncCommand<T>
    {
        public SafeAsyncCommand(Func<T, Task> execute, Func<T, bool> canExecute = null, bool allowConcurrentExecutions = false)
            : base(execute, canExecute, allowConcurrentExecutions)
        {
        }

        public SafeAsyncCommand(Func<T, CancellationToken, Task> execute, Func<T, bool> canExecute = null, bool allowConcurrentExecutions = false)
            : base(execute, canExecute, allowConcurrentExecutions)
        {
        }

        protected override async Task ExecuteAsyncImpl(object parameter)
        {
            if (!SafeCommandSettings.CanExecute) return;

            SafeCommandSettings.Pause();
            await base.ExecuteAsyncImpl(parameter);
        }
    }

I thought this is working but I saw users were able to do it again. Do I miss some knowledge about async methods or static thread safe?

Thanks in advance


Solution

  • Instead of delaying things, consider using AsyncLock by Stephen Cleary or lookup Interlocked.CompareExchange.

    As far as I can tell from here, you shouldn't use a static CanExecute in your case, as it locks all commands using your "safe" command at once. And there is the possibility of race conditions, since you aren't changing the value of CanExecute locked.