You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An MVVM user interface binds input controls such as buttons or context menus to ICommand implementations in a viewmodel. Usually, we use DelegateCommands or an async counterpart that implements ICommand for this purpose. Commands on a user interface may be started in parallel which causes the underlying command handlers to run in parallel. Command's "canExecute" function can be used to check if one command is executing but this approach is not thread safe and still allows user input to happen in parallel.
In order to synchronize parallel execution of commands, we created a new thing we called "CommandGroup" (the name can be discussed, of course). The aim is to toggle an execution flag in a thread-safe way to avoid that a command can be started while another command is already running.
API Changes
Here I have reduced-to-the-max copy of the working code. I also have unit tests ready. (I'm just holding unnecessary things back, in case the feedback is, that Prism doesn't want such a thing to integrate).
publicclassCommandGroup:BindableObject,ICommandGroup{privateconstlongNotRunning=0;privateconstlongRunning=1;privatereadonlystring_name;privatelong_currentState;/// <summary>/// Initializes a new instance of the <see cref="CommandGroup"/> class./// </summary>publicCommandGroup(): this(null){}/// <summary>/// Initializes a new instance of the <see cref="CommandGroup"/> class/// with <param name="name">the name of the instance</param> for debugging purposes./// </summary>publicCommandGroup(stringname){_name=name?? Guid.NewGuid().ToString().Substring(0,5).ToUpperInvariant();}publicboolIsAnyRunning=> Interlocked.Read(ref _currentState)==Running;
#region Create methods for Xamarin.Forms.Command
// Removed for brevity
#endregion
#region Create methods for AsyncRelayCommand
public AsyncRelayCommand CreateAsyncRelayCommand(Func<Task>execute){return CreateAsyncRelayCommand(
execute,()=>true);}public AsyncRelayCommand CreateAsyncRelayCommand(Func<Task>execute,Func<bool>canExecute){return CreateCommandWithFactory(
execute,
canExecute,(e,ce)=>new AsyncRelayCommand(e, ce));}publicAsyncRelayCommand<TParameter>CreateAsyncRelayCommand<TParameter>(Func<TParameter,Task>execute){return CreateAsyncRelayCommand(
execute,()=>true);}publicAsyncRelayCommand<TParameter>CreateAsyncRelayCommand<TParameter>(Func<TParameter,Task>execute,Func<bool>canExecute){return CreateAsyncRelayCommand(
execute,_ => canExecute());}publicAsyncRelayCommand<TParameter>CreateAsyncRelayCommand<TParameter>(Func<TParameter,Task>execute,Func<TParameter,bool>canExecute){return CreateCommandWithFactory(
execute,
canExecute,(e,ce)=>newAsyncRelayCommand<TParameter>(e,p => ce(p)));}
#endregion
private TCommand CreateCommandWithFactory<TCommand>(Func<Task>execute,Func<bool>canExecute,Func<Func<Task>,Func<bool>,TCommand>factory)whereTCommand:ICommand{returnCreateCommandWithFactory<TCommand,object>(_ => execute(),_ => canExecute(),(e,ce)=> factory(()=> e(null),()=> ce(null)));}private TCommand CreateCommandWithFactory<TCommand,TParameter>(Func<TParameter,Task>execute,Func<TParameter,bool>canExecute,Func<Func<TParameter,Task>,Func<TParameter,bool>,TCommand>factory)whereTCommand:ICommand{varcommand= factory(async p =>{if(Interlocked.CompareExchange(ref _currentState, Running, NotRunning)==NotRunning){ OnPropertyChanged(nameof(IsAnyRunning)); Debug.WriteLine($"CommandGroup {_name}: Command execution started");try{await execute(p);}finally{ Debug.WriteLine($"CommandGroup {_name}: Command execution finished"); Interlocked.Exchange(ref _currentState, NotRunning); OnPropertyChanged(nameof(IsAnyRunning));}}else{ Debug.WriteLine($"CommandGroup {_name}: Command execution skipped");}},p =>{if(!IsAnyRunning){return canExecute(p);} Debug.WriteLine($"CommandGroup {_name}: CanExecute skipped");returnfalse;});returncommand;}}
API Usage
You can use CommandGroup with just minimal changes in your viewmodels. In fact, we upgraded our code with search-replace.
Summary
An MVVM user interface binds input controls such as buttons or context menus to ICommand implementations in a viewmodel. Usually, we use DelegateCommands or an async counterpart that implements ICommand for this purpose. Commands on a user interface may be started in parallel which causes the underlying command handlers to run in parallel. Command's "canExecute" function can be used to check if one command is executing but this approach is not thread safe and still allows user input to happen in parallel.
In order to synchronize parallel execution of commands, we created a new thing we called "CommandGroup" (the name can be discussed, of course). The aim is to toggle an execution flag in a thread-safe way to avoid that a command can be started while another command is already running.
API Changes
Here I have reduced-to-the-max copy of the working code. I also have unit tests ready. (I'm just holding unnecessary things back, in case the feedback is, that Prism doesn't want such a thing to integrate).
API Usage
You can use CommandGroup with just minimal changes in your viewmodels. In fact, we upgraded our code with search-replace.
Before:
After:
Your Feedback
What do you think about the proposed solution?
Is this a non-problem?
How do other people solve this issue?
Links
The text was updated successfully, but these errors were encountered: