ThommyB ThommyB - 3 months ago 52
C# Question

How to use async / await with a method returning an ObservableCollection<T>

I am writing a WPF application using MVVM pattern. I must say I am not (yet) familiar with the .NET async programming model (just learning it) but was writing multi-threaded applications in C/C++ and Java for many years.

The idea is to start long running queries in a separate thread from the UI thread so I have a chance to display a spinner or progress bar while the operation is running.

Most of my internal service methods return collections or other complex types like DTO's etc. The results are usually bound to user controls such as GridViews, ListViews etc. I always get compiler errors saying that my return types do not contain a definition for 'GetAwaiter'. So I must be doing something wrong.

Here is an example of such a method in my ViewModel that calls a service which does a DB query and returns an ObservableCollection.

private async void GetInvoices()
{
IsOperationRunning = true; //Binding for a RadBusyIndicator
Invoices = await _uiContractService.GetInvoices(SelectedInvoiceState, SelectedSupplierItem.Id, SelectedInvoiceDateFilter, FilterStartDate, FilterEndDate);
IsOperationRunning = false;
RaisePropertyChanged(() => Invoices);
if (Invoices.Count == 0)
SendUserInfoMsg($"NO invoices matched your search criteria.", UiUserNotificationMessageType.Warning);
}


The compiler error in this case is:
ObservableCollection<InvoiceDto>' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'ObservableCollection<InvoiceDto>' could be found


The Invoices collection and the service are declared as

public ObservableCollection<InvoiceDto> Invoices { get; set; }
var _uiContractService = SimpleIoc.Default.GetInstance<UiContractService>();


The signature of the service method is as follows:

public ObservableCollection<InvoiceDto> GetInvoices(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end)


Any help to guide me into the right direction is greatly appreciated.

Answer

was writing multi-threaded applications in C/C++ and Java for many years.

This experience is probably misleading you. Asynchronous code is very different than multithreaded code.

await has nothing to do with threads. Or put another way, it doesn't increase the number of threads used; it decreases them.

I recommend you first read my async intro to get an idea of what async/await actually do.

Then, you can proceed one of two ways. The first way is not ideal, but is what you actually asked for:

The idea is to start long running queries in a separate thread from the UI thread so I have a chance to display a spinner or progress bar while the operation is running.

The first approach is to multithread. In this case, you can use async/await as a convenient way to retrieve results from an operation running on another thread (including clean, correct exception stacks):

private async Task GetInvoicesAsync()
{
  IsOperationRunning = true; //Binding for a RadBusyIndicator
  Invoices = await Task.Run(() => _uiContractService.GetInvoices(SelectedInvoiceState, SelectedSupplierItem.Id, SelectedInvoiceDateFilter, FilterStartDate, FilterEndDate));
  IsOperationRunning = false;
  RaisePropertyChanged(() => Invoices);
  if (Invoices.Count == 0)
    SendUserInfoMsg($"NO invoices matched your search criteria.", UiUserNotificationMessageType.Warning);
}

Note that it is the Task.Run that introduces multithreading - it schedules work to the thread pool. The async/await is how the UI thread pretends the operation is asynchronous.

This can be called from an event handler (or ICommand) as such:

public async void EventHandler(object sender, ...)
{
  await GetInvoicesAsync();
}

Note that only event handlers should be async void. See my article on async best practices for more information.


But the first approach isn't ideal, because it's not really asynchronous. It's what I call "fake-asynchronous"; that is, the UI is pretending it's asynchronous, but the actual operation is just synchronously blocking another thread.

When writing true asynchronous code, it's easier to start at the other end - not the top-level UI methods, but the low-level actual API calls. If your services are HTTP services, look into HttpClient; if they're WCF services, regenerate your proxies with asynchronous APIs enabled.

Once you have the lowest-level asynchronous APIs available, start using them. Eventually, you'll end up with an asynchronous GetInvoices:

public async Task<List<InvoiceDto>> GetInvoicesAsync(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end);

(note that I removed ObservableCollection from your service layer since it is a UI-oriented type)

Then you can call this method as such:

private async Task GetInvoicesAsync()
{
  IsOperationRunning = true; //Binding for a RadBusyIndicator
  Invoices = new ObservableCollection<InvoiceDto>(await _uiContractService.GetInvoicesAsync(SelectedInvoiceState, SelectedSupplierItem.Id, SelectedInvoiceDateFilter, FilterStartDate, FilterEndDate));
  IsOperationRunning = false;
  RaisePropertyChanged(() => Invoices);
  if (Invoices.Count == 0)
    SendUserInfoMsg($"NO invoices matched your search criteria.", UiUserNotificationMessageType.Warning);
}

This time, there's no Task.Run or any other explicit usage of thread pool threads.

For more details, see the "Transform" parts of my brownfield async article.

Comments