我正在使用MVVM模式编写WPF应用程序。我必须说我(还)熟悉.NET异步编程模型(只是学习它),但多年来一直在用C / C ++和Java编写多线程应用程序。
我们的想法是在UI线程的单独线程中启动长时间运行的查询,这样我就有机会在操作运行时显示微调器或进度条。
我的大多数内部服务方法都返回集合或其他复杂类型,如DTO等。结果通常绑定到用户控件,如GridViews,ListViews等。我总是遇到编译器错误,说我的返回类型没有包含' GetAwaiter'的定义。所以我一定做错了。
以下是我的 ViewModel 中的这种方法的示例,该方法调用执行数据库查询并返回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);
}
这种情况下的编译器错误是:
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
Invoices集合和服务声明为
public ObservableCollection<InvoiceDto> Invoices { get; set; }
var _uiContractService = SimpleIoc.Default.GetInstance<UiContractService>();
服务方法的签名如下:
public ObservableCollection<InvoiceDto> GetInvoices(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end)
非常感谢任何帮助我指导正确方向的帮助。
答案 0 :(得分:4)
多年来一直在用C / C ++和Java编写多线程应用程序。
这种经历可能会误导你。异步代码非常与多线程代码不同。
await
与线程无关。换句话说,它并没有增加使用的线程数;它减少它们。
我建议您先阅读我的async
intro,以了解async
/ await
实际执行的操作。
然后,您可以采用以下两种方式之一。第一种方式是不理想,但实际上是要求:
我们的想法是在UI线程的单独线程中启动长时间运行的查询,这样我就有机会在操作运行时显示微调器或进度条。
第一种方法是多线程。在这种情况下,您可以使用async
/ await
作为从另一个线程上运行的操作(包括干净,正确的异常堆栈)检索结果的便捷方法:
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);
}
请注意,引入多线程的是Task.Run
- 它会为线程池安排工作。 async
/ await
是UI线程假装操作的方式是异步的。
这可以从事件处理程序(或ICommand
)调用,如下所示:
public async void EventHandler(object sender, ...)
{
await GetInvoicesAsync();
}
请注意,只有事件处理程序应为async void
。有关详细信息,请参阅我的article on async
best practices。
但第一种方法并不理想,因为它并非真正异步。这就是我所说的&#34;假异步&#34 ;;也就是说,UI 假装它是异步的,但实际操作只是同步阻塞另一个线程。
编写真正的异步代码时,更容易从其他端开始 - 而不是顶级UI方法,而是低级实际API调用。如果您的服务是HTTP服务,请查看HttpClient
;如果他们是WCF服务,请在启用异步API的情况下重新生成代理。
一旦有了最低级别的异步API,就可以开始使用它们了。最终,您将以异步GetInvoices
:
public async Task<List<InvoiceDto>> GetInvoicesAsync(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end);
(请注意,我从服务层删除了ObservableCollection
,因为它是面向UI的类型)
然后你可以这样调用这个方法:
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);
}
这一次,没有Task.Run
或任何其他明确使用的线程池线程。
有关详细信息,请参阅&#34;转换&#34;部分brownfield async
article。
答案 1 :(得分:1)
您的代码应返回Task。
public Task<ObservableCollection<InvoiceDto>> GetInvoices(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end)
{
// your code
}
答案 2 :(得分:1)
GetInvoices是否正在执行异步操作?如果是,其签名应为:
public async Task<ObservableCollection<InvoiceDto>> GetInvoices(InvoiceState invoiceState, Guid supplierId, UiInvoiceDateFilters dateType, DateTime begin, DateTime end)
。
如果您不是,那么您不必担心异步并从外部GetInvoices
方法中删除async / await关键字。
如果您想要await
某事,则应该返回Task
或Task<T>
。 (我甚至不会进入异步空方法)。
答案 3 :(得分:0)
你做了一个&#34;假的&#34; 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);
});
}
这可能被认为是错误的代码,这里只是为了展示如何通过使用Async后缀调用方法使其正式等待
无论如何这是wpf,而不是asp.net或web应用程序。因此,如果它是一个纯桌面GUI,那么在后台运行db任务是有意义的。如果它是在应用程序服务器上而不是在客户端上,则会出现更多问题。
Invoices = await _uiContractService.GetInvoicesAsync(SelectedInvoiceState, SelectedSupplierItem.Id, SelectedInvoiceDateFilter, FilterStartDate, FilterEndDate);
请注意,错误和不理想之间存在很大差异,并且并非总是能够立即支持升级到最新技术。
另一点,你不会问,但看起来很奇怪的是你如何改变财产。 一个更方便的地方是酒店的设置者。 然后,除非您使用底层私人成员,否则您不需要在作业后调用它。
我还要在收集正在进行的工作之前清理收藏品,但这些都是个人喜好。