Home > Software design >  Why task is not running even when execution is inside the task method?
Why task is not running even when execution is inside the task method?

Time:12-04

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:

enter image description here

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.

  • Related