Alan Wayne Alan Wayne - 2 months ago 12
C# Question

How does WPF treat a field awaiting a response from Async/Await?

I'm trying to clarify my understanding of Async/Await. (I have read much here on SO, but haven't gotten a clear grasp as yet). Lets say I have a window constructor that begins a long process:

// Constructor
public MainWindowViewModel() : base()
{
init();
........
}

private async void init()
{
PatientList = await GetPatientList(); <--- A VERY LONG PROCESS
.....
}

private ObservableCollection<ViewPatient> PatientList;

private async Task<ObservableCollection<ViewPatient>> GetPatientList()
{
return new ObservableCollection<ViewPatient>(await MedicalClient.GetAllPatientsAsync());
}


So, if I get this right, await will start the
GetPatientList()
method, and then
immediately return to the caller (the
MainWindowViewModel()
which will then complete and display the window.

Now, in the window I have a search button on the names in the list. After a few quick jumps, the finder calls:

private async Task<ObservableCollection<ViewPatient>> GetPatientListFromName(string lastname, string firstname, string birthdate)
{
string birth = string.Empty;
if (!string.IsNullOrWhiteSpace(birthdate))
{
// regex to look for pattern: 00/00/0000, 0/00/0000, 00/0/0000
Regex regex = new Regex(@"^\d{1,2}\/\d{1,2}\/\d{4}$");
Match x = regex.Match(birthdate);
if (!x.Success) return null;
birth = birthdate;
}

string last = lastname ?? string.Empty;
string first = firstname ?? string.Empty;

// PatientList = await GetPatientList(); <--Is This Needed?

var z = PatientList.Where(p =>
p.Lastname.StartsWith(last.ToUpper()) &&
p.Firstname.StartsWith(first.ToUpper()) &&
((DateTime)p.Birthdate).ToShortDateString().StartsWith(birth));

return new ObservableCollection<ViewPatient>(z);

}


So now my questions are:


  1. If the
    GetPatientList()
    has not completed (so
    PatientList
    is not known) by the time
    GetPatientListFromName(...)
    needs it, will
    GetPatientListFromName(...)
    automatically wait for its completion prior to continuing into the LINQ expression? Or is something more needed?

  2. If I include the (commented out)
    PatientList = await GetPatientList();
    will
    GetPatientList()
    be started again --even if it is already running?


Answer

I'm trying to clarify my understanding of Async/Await.

I suggest that you start off with my tutorial, which has links to (IMO) the best follow-up resources at the bottom. Since you're writing a WPF app, I would also recommend my article series on async MVVM (particularly the one on async data binding). SO is great for Q&A, but it's not really meant to be a tutorial/learning site.

The first thing to realize about await is how it splits up its async method. In particular, this:

private async void init()
{
  PatientList = await GetPatientList();
}

is essentially the same as this:

private async void init()
{
  var task = GetPatientList();
  var result = await task;
  PatientList = result;
}

This should make it clear that

  1. GetPatientList is invoked before the await begins.
  2. PatientList is assigned after the await completes.

With that in mind, you can answer your questions:

If the GetPatientList() has not completed (so PatientList is not known) by the time GetPatientListFromName(...) needs it, will GetPatientListFromName(...) automatically wait for its completion prior to continuing into the linq expression?

No. The PatientList will be null until the await in init is completed.

If I include the (commented out) PatientList = await GetPatientList(); will GetPatientList() be started again --even if it is already running?

Yes. This will invoke GetPatientList a second time, creating another task.


To solve your actual problem (that is, only load PatientList once but allow the search button to work appropriately), you can do this a few different ways.

One approach is to save an "initialize" task, as such:

private Task _initTask;
public MainWindowViewModel() : base()
{
  _initTask = InitAsync();
  ...
}

private async Task InitAsync()
{
  PatientList = await GetPatientList();
  ...
}

private async Task<ObservableCollection<ViewPatient>> GetPatientListFromName(string lastname, string firstname, string birthdate)
{
  ...
  string last = lastname ?? string.Empty;
  string first = firstname ?? string.Empty;

  // Ensure PatientList is loaded.
  await _initTask;

  var z = PatientList.Where...
}

This will cause your Search button to (asynchronously) wait for PatientList if it is not already complete. It's perfectly safe to await tasks that are already completed, and also to await tasks multiple times.

A nice side benefit of this approach is that the async void is transformed to async Task. It's a best practice to avoid async void.