如何使用返回ObservableCollection <t>的方法使用async / await

时间:2016-08-26 19:34:39

标签: c# wpf async-await

我正在使用MVVM模式编写WPF应用程序。我必须说我(还)熟悉.NET异步编程模型(只是学习它),但多年来一直在用C / C ++和Java编写多线程应用程序。

我们的想法是在UI线程的单独线程中启动长时间运行的查询,这样我就有机会在操作运行时显示微调器或进度条。

我的大多数内部服务方法都返回集合或其他复杂类型,如DTO等。结果通常绑定到用户控件,如GridViews,ListViews等。我总是遇到编译器错误,说我的返回类型没有包含&#39; GetAwaiter&#39;的定义。所以我一定做错了。

以下是我的 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)

非常感谢任何帮助我指导正确方向的帮助。

4 个答案:

答案 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某事,则应该返回TaskTask<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);

请注意,错误和不理想之间存在很大差异,并且并非总是能够立即支持升级到最新技术。

另一点,你不会问,但看起来很奇怪的是你如何改变财产。 一个更方便的地方是酒店的设置者。  然后,除非您使用底层私人成员,否则您不需要在作业后调用它。

我还要在收集正在进行的工作之前清理收藏品,但这些都是个人喜好。