i want to start a task when a relay command is called, however i want to disable the button as long as that task is running
take this example
private ICommand update;
public ICommand Update
{
get
{
if (update == null)
{
update = new RelayCommand(
param => Task.Factory.StartNew(()=> StartUpdate()),
param => true); //true means the button will always be enabled
}
return update;
}
}
what is the best way to check if that task is running?
here is my solution but not sure if its the best way
class Vm : ObservableObject
{
Task T;
public Vm()
{
T = new Task(() => doStuff());
}
private ICommand myCommand;
public ICommand MyCommand
{
get { return myCommand ?? (myCommand = new RelayCommand( p => { T = new Task(() => doStuff()); T.Start(); }, p => T.Status != TaskStatus.Running)); }
}
private void doStuff()
{
System.Threading.Thread.Sleep(5000);
}
}
Update : Every answer here works fine, but still they dont agree with each other, and i just reached a 100 reputation , i start a bounty whenever i reach 100, so what i am looking for is an implementation for an optimal non memory leaking asynchronous RelayCommand that executes within a task in .net 4.0
I strongly recommend that you avoid new Task
as well as Task.Factory.StartNew
. The proper way to start an asynchronous task on a background thread is Task.Run
.
You can create an asynchronous RelayCommand
easily using this pattern:
private bool updateInProgress;
private ICommand update;
public ICommand Update
{
get
{
if (update == null)
{
update = new RelayCommand(
async () =>
{
updateInProgress = true;
Update.RaiseCanExecuteChanged();
await Task.Run(() => StartUpdate());
updateInProgress = false;
Update.RaiseCanExecuteChanged();
},
() => !updateInProgress);
}
return update;
}
}
I think, you can use this implementation of AsyncCommand.
public class AsyncCommand : ICommand, IDisposable
{
private readonly BackgroundWorker _backgroundWorker = new BackgroundWorker {WorkerSupportsCancellation = true};
private readonly Func<bool> _canExecute;
public AsyncCommand(Action action, Func<bool> canExecute = null, Action<object> completed = null,
Action<Exception> error = null)
{
_backgroundWorker.DoWork += (s, e) =>
{
CommandManager.InvalidateRequerySuggested();
action();
};
_backgroundWorker.RunWorkerCompleted += (s, e) =>
{
if (completed != null && e.Error == null)
completed(e.Result);
if (error != null && e.Error != null)
error(e.Error);
CommandManager.InvalidateRequerySuggested();
};
_canExecute = canExecute;
}
public void Cancel()
{
if (_backgroundWorker.IsBusy)
_backgroundWorker.CancelAsync();
}
public bool CanExecute(object parameter)
{
return _canExecute == null
? !_backgroundWorker.IsBusy
: !_backgroundWorker.IsBusy && _canExecute();
}
public void Execute(object parameter)
{
_backgroundWorker.RunWorkerAsync();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_backgroundWorker != null)
_backgroundWorker.Dispose();
}
}
}
So your solution to use RelayCommand almost works. The problem is that the UI won't immediately update after the task finishes running. This is because something needs to trigger the ICommand's CanExecuteChanged event in order for the UI to properly update.
One way to solve this problem is by creating a new kind of ICommand. For example:
class AsyncRelayCommand : ICommand
{
private Func<object, Task> _action;
private Task _task;
public AsyncRelayCommand(Func<object,Task> action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return _task == null || _task.IsCompleted;
}
public event EventHandler CanExecuteChanged;
public async void Execute(object parameter)
{
_task = _action(parameter);
OnCanExecuteChanged();
await _task;
OnCanExecuteChanged();
}
private void OnCanExecuteChanged()
{
var handler = this.CanExecuteChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
Now your view model can do something like the following
private ICommand myCommand;
public ICommand MyCommand
{
get { return myCommand ?? (myCommand = new AsyncRelayCommand(p => Task.Factory.StartNew(doStuff))); }
}
private void doStuff()
{
System.Threading.Thread.Sleep(5000);
}
Or you could make your doStuff function an "async" function like so
private ICommand myCommand2;
public ICommand MyCommand2
{
get { return myCommand2 ?? (myCommand2 = new AsyncRelayCommand(p => doStuff2())); }
}
private async Task doStuff2()
{
await Task.Delay(5000);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With