【问题标题】:UI is blocking with async await methodsUI 被异步等待方法阻塞
【发布时间】:2020-11-17 18:51:14
【问题描述】:

我在一个窗口上有 2 个DataGrid,当您单击顶部 DataGrid 中的一行时,它会调用 CurrentChanged 事件以获取底部 DataGrid 的数据以显示该汽车品牌的模型。单击一行时 UI 会被阻止,因为该方法需要一段时间才能完成。如何让 async await 正常工作,以免 UI 被阻塞?

public class MainWindowViewModel
{
    private ObservableCollection<Company> carCollectionOC = new ObservableCollection<Company>();
    private ObservableCollection<Vehicle> vehicleOC = new ObservableCollection<Vehicle>();
    private VehicleService vehicleService;
    public ICollectionView CarCollection { get; set; }
    public ICollectionView VehicleCollection { get; set; }

    public MainWindowViewModel()
    {
        this.carCollectionOC.Add(new Company() { Brand = "Ford", Established = 1903 });
        this.carCollectionOC.Add(new Company() { Brand = "Vauxhall", Established = 1857 });
        this.CarCollection = new ListCollectionView(this.carCollectionOC);
        this.CarCollection.CurrentChanged += CarCollection_CurrentChanged;
        this.vehicleService = new VehicleService();
        this.VehicleCollection = new ListCollectionView(this.vehicleOC);
    }

    private async void CarCollection_CurrentChanged(object sender, EventArgs e)
    {
        Company company = (Company)(sender as ICollectionView).CurrentItem;
        this.vehicleOC = await this.GetVehiclesAsync(company.Brand);
    }

    private async Task<ObservableCollection<Vehicle>> GetVehiclesAsync(string carBrand)
    {
        this.vehicleOC.Clear();
        foreach (var item in await this.vehicleService.GetVehicleListAsync(carBrand))
            this.vehicleOC.Add(item);
        Console.WriteLine("finishing GetVehiclesAsync");
        return this.vehicleOC;

    }
}

GetVehicleListAsync 是:

    public Task<List<Vehicle>> GetVehicleListAsync(string carBrand)
    {
        for (var i = 600000000; i >= 0; i--)
        {
            // simulate a long process
        }
        return Task.FromResult(this.vehicleList.Where(item => item.Brand == carBrand).ToList());
    }

【问题讨论】:

  • 您的GetVehicleListAsync 实际上并不是异步的——它在调用它的线程上完成所有工作,然后使用Task.FromResult 创建一个已经完成的Task。为了真正异步,GetVehicleListAsync 需要快速返回,并返回尚未完成的Task。稍后当工作完成时,它将完成Task。如果您的“长进程”受 CPU 限制(不是 IO 限制,或使用 sleepds 等),那么使用 Task.Run 是将这项工作移至后台线程并返回 Task 的简单方法操作完成。
  • 简而言之,我认为您应该多研究一下 async 和 await 模式和任务。您似乎在基础知识方面遇到了问题。在这个阶段进行一点研究将比这些问题更有益

标签: c# wpf async-await


【解决方案1】:

GetVehicleListAsync 不是异步方法;它是一个同步方法,返回一个Task&lt;Vehicle&gt;will 块。

由于您没有执行任何异步操作,即 I/O,您应该使用 Task.Run 将昂贵的代码卸载到 ThreadPool,从而使 UI 线程畅通无阻:

private async void CarCollection_CurrentChanged(object sender, EventArgs e)
{
    Company company = (Company)(sender as ICollectionView).CurrentItem;
    this.vehicleOC.Clear();
    foreach (var vehicle in await Task.Run(() => this.GetVehicles(company.Brand)))
        this.vehicleOC.Add(vehicle);
}

private IEnumerable<Vehicle> GetVehicles(string carBrand)
{
    return this.vehicleService.GetVehicleList(carBrand));
    Console.WriteLine("finishing GetVehicles");
}

public List<Vehicle> GetVehicleList(string carBrand)
{
    for (var i = 600000000; i >= 0; i--)
    {
        // simulate a long process
    }
    return this.vehicleList.Where(item => item.Brand == carBrand).ToList();
}

GetVehiclesAsyncGetVehicleListAsync 本质上是同步的,应该这样实现。

【讨论】:

  • 在这种特定情况下这可能会失败,因为如果ObservableCollection 绑定到 UI(这里就是)- 从非 UI 线程添加项目将失败。
  • GetVehicleListAsync 是否是异步方法尚有争议。在我看来it is,但还有其他意见。
  • 此实现错误 - System.NotSupportedException: '这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。'我该如何阻止这种情况?
  • Johnathan 我不喜欢在循环中调用Dispatcher.Invoke,因为它会增加大量的同步开销。在这种情况下没有必要这样做。您可以在后台线程中获取数据,然后在 UI 线程中执行更新 UI 的循环。
  • @chris var data = await Task.Run(() =&gt; this.vehicleService.GetVehicleList(carBrand));,然后使用数据填充 UI。比起使用Dispatcher,我更喜欢这个。 Stephen Cleary 的建议"Don't Use Task.Run in the Implementation" 主要适用于库代码。在应用程序级别编写 UI-helper 方法时,可以走捷径并在这里和那里使用 Task.Run 恕我直言。
【解决方案2】:

您的GetVehicleListAsync 方法实际上不是异步的。 将其更改为:

public Task<List<Vehicle>> GetVehicleListAsync(string carBrand)
{
    return Task.Run(() => 
    {
       for (var i = 600000000; i >= 0; i--)
       {
           // simulate a long process
       }
       return this.vehicleList.Where(item => item.Brand == carBrand).ToList();
    }
}

【讨论】:

猜你喜欢
  • 2016-03-13
  • 1970-01-01
  • 2022-01-04
  • 1970-01-01
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 2013-06-04
  • 1970-01-01
相关资源
最近更新 更多