So this is WPF MVVM .NET 4.8 WCT.
I have an AsyncRelayCommand
in my VM class defined like this:
private AsyncRelayCommand _StartSyncCommand;
public AsyncRelayCommand StartSyncCommand
{
get
{
_StartSyncCommand ??= new AsyncRelayCommand(Pump, () => !_StartSyncCommand.IsRunning);
return _StartSyncCommand;
}
}
The actual task method contains an async iterator and looks like this:
private async Task Pump(CancellationToken token)
{
OnPropertyChanged(nameof(IsBusy));
try
{
await foreach (var item in applicationService.FetchItems())
{
token.ThrowIfCancellationRequested();
...
}
}
catch(Exception ee)
{
...
}
finally
{
...
}
}
This method raises property change notification on IsBusy
property (to show wait cursor in the UI). However when I check the status of StartSyncCommand
in the property, it tells me that it is not running.
public bool IsBusy => StartSyncCommand.IsRunning;
I can't see why this should be the case. The method is actually running when the property change notification occurs. I can see the method in the call stack.
What am I missing here?
Update
This is getting weirder. StartSyncCommand.ExecutionTask
itself is null
while I'm inside the task method:
CodePudding user response:
All asynchronous methods begin executing synchronously, as I explain on my blog. So it's not surprising to me that at the beginning of your async
method that its task is null (and thus presumably the busy property is false). I haven't looked at the WCT code, but I expect under the hood it looks something like this:
ExecutionTask = execute();
IsRunning = true;
(where execute
is a delegate pointing to your Pump
method)
Since Pump
hasn't hit its first await
yet, it hasn't returned a task yet, so ExecutionTask
hasn't been set.
You can work around this by doing an await Task.Yield();
at the beginning of Pump
, but IMO a cleaner solution would be to move the OnPropertyChanged
call outside Pump
, or remove it entirely and just forward the property change notifications from IsRunning
.
CodePudding user response:
For anyone facing this problem, here is what I ended up with:
private AsyncRelayCommand _StartSyncCommand;
public AsyncRelayCommand StartSyncCommand
{
get
{
_StartSyncCommand ??= new AsyncRelayCommand(token =>
{
return Task.Run(async () =>
{
OnPropertyChanged(nameof(IsBusy));
await Pump(token);
});
},
() => !_StartSyncCommand.IsRunning);
return _StartSyncCommand;
}
}
As @Stephen correctly pointed out in his answer, AsyncRelayCommand
does not create an run the task it created from the supplied async method till it hits the first await
in the method, which means that trying to check IsRunning
or ExecutionTask
on the command object will not return desired results before that await
line is reached. With a Task.Run()
call, I'm force-creating and running the task before issuing my property change notifications.