Reputation: 1556
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.
Upvotes: 3
Views: 4603
Reputation:
You con do a "fake" asynchronous task out of your GetInvoices:
public async Task<ObservableCollection<InvoiceDto>> GetInvoicesAsync(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end)
{
return await Task.Run( () =>
{
return GetInvoices(invoiceState, supplierId, dateType, begin, end);
});
}
This could be considered bad code, here it's simply to show how you can make it formally await-able by calling the method with Async suffix
Anyway this is wpf, not a asp.net or a web app. So if it's a pure desktop GUI, it would make sense to run a db task in background. It's more problematic if that were on an application server and not on a client..
Invoices = await _uiContractService.GetInvoicesAsync(SelectedInvoiceState, SelectedSupplierItem.Id, SelectedInvoiceDateFilter, FilterStartDate, FilterEndDate);
Notice that there is a big difference between a wrong and a not ideal thing and that it is not always the case that you can have immediate support for an upgrade to the latest technology.
Another point that you're not asking but looks odd is how you raise property changed. A much more convenient place is the setter of the property. Then you don't need to call it after an assignment unless you are using the underlying private member.
I would have also cleaned the collection before just to enphasize the work in progress but these are personal preferences.
Upvotes: 0
Reputation: 456507
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.
Upvotes: 6
Reputation: 4652
Is GetInvoices doing anything async? If it is, its signature should be:
public async Task<ObservableCollection<InvoiceDto>> GetInvoices(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end)
.
If you aren't, then you shouldn't worry about async and remove the async/await keywords from the outer GetInvoices
method.
If you want to await
something, it should return Task
or Task<T>
. (I won't even go into async void methods).
Upvotes: 1
Reputation: 5312
Your code should return a Task.
public Task<ObservableCollection<InvoiceDto>> GetInvoices(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end)
{
// your code
}
Upvotes: 1