Async/Await with Linq Extensions

We all have read and worked on Async/Await and that too many a times. I might not be describing something highly technical but would like to share my learning.

Today while working on one of my WPF application I encountered a strange, rather an unexpected, issue with async/await and Linq extensions where Async/Await didn’t worked with Linq extensions. Let us  try and understand the whole scenario in detail.

I am having a DataModel class in my Data Layer and by using the below asynchronous method I am  fetching data from database and populating a generic collection. It works on the basis of parameter project provided.

//DataModel.cs
public async Task<List<TClass>> PlotChart(string project)
{
	//Data Logic
}

In my View Model Dashboard.cs I am having another PlotChart  method which makes call to the PlotChart method of  DataModel.cs.  This View Model method applies some business logic and ultimately plots some charts on the dashboard.

//DashboardViewModel.cs 
private async Task PlotChart(MainWindow window, string project)
{
	var data = await _db.PlotChart(project);
	//Business Logic
}

In my Xaml i am using two boolean properties (IsBusyPie & IsBusyLine) to show/hide the progress bar and two string properties (BusyMessagePie & BusyMessageLine) two change the message at run time.

<xctk:BusyIndicator
IsBusy=”{Binding IsBusyPie}”
BusyContent=”{Binding BusyMessagePie}” >
<WrapPanel x:Name=”ProjectPanel”/>
</xctk:BusyIndicator>

<xctk:BusyIndicator
IsBusy=”{Binding IsBusyLine}”
BusyContent=”{Binding BusyMessageLine}” >
<WrapPanel x:Name=”BranchPanel”/>
</xctk:BusyIndicator>

There are three approaches which i tried to get the job done, which are described below and their pros and cons also.

Approach 1

Initially I tried calling my ViewModel PlotChart method in view model’s constructor itself using Array.ForEach linq extension.

//In Constructor of ViewModel
IsBusyPie = IsBusyLine = true;
Array.ForEach(projects, project => 
{
	PlotChart(window,project);	
});
IsBusyPie = IsBusyLine = false;

The issue with this approach was, two calls to the function plotchart was made by the Array.Foreach and the boolean values were assigned false. This made UI stuck and there was no progress bar.

Explanation: In this approach the array made asynchronous call and when it hits the await of PlotChart function, control returns. It does that twice (assuming the array has two values).The parent method comes out of that loop and sets

IsBusyPie = IsBusyLine = false;

And some time after when the result of the awaits is returned and it carries on inside the PlotCharts.

Approach 2

Then I changed my approach and I created another function named PlotChart and made calls to my asynchronous method using await keyword (As I could not use await in constructor i created another function). The issue still not resolved although I used await keyword with the calls.

//New Function
private async Task PlotChart(MainWindow window)
{
	IsBusyPie = IsBusyLine = true;
	Array.ForEach(projects, async project =>
		await PlotChart(window, project));
	IsBusyPie = IsBusyLine = false;
}

ExplanationList<T>.ForEach doesn’t play particularly well with async (neither does LINQ-to-objects, for the same reasons). Possibly the lambda is being seen by the compiler as an async void and the await does nothing.

Approach 3

After failing in the above two approaches i decided to iterate the array and make two conventional calls using foreach. And to my surprise it worked fine. My all progress bars showing properly, my UI is not stuck and my views gets updated simultaneously also.

//New Function 
private async Task PlotChart(MainWindow window)
{
	IsBusyPie = IsBusyLine = true;
	foreach (var project in projects)
	{
		await PlotChart(window, project);
	}
	IsBusyPie = IsBusyLine = false;
}

Explanation: This approach is a mix asynchronous and synchronous process. Here when the loop hits the first asynchronous method it awaits for its result and as soon as it gets the output, it updates the View and makes another asynchronous call to the method.

Conclusion: After working with different approaches, it suggests that you should use the foreach loop and await each call instead of using LINQ-to-objects.