在调度程序上异步运行操作不会完成或冻结 UI

本文关键字:UI 冻结 操作 调度程序 调度 程序上 异步 运行 | 更新日期: 2023-09-27 17:59:39

我只想异步运行 Drop 操作,以便在移动大量内容时显示繁忙的对话框。因为源集合只能由Dispatcher访问,我需要调用它。

这样,等待的调用永远不会完成/对话框永远不会关闭。
出于上述相同原因,在此处使用 Invoke 而不是 InvokeAsync 会导致NotSupportedException

public async void Drop(IDropInfo dropInfo)
{
    MainViewModel.Current.ShowBusyDialog();
    await Task.Run(() =>
    {
        // This would crash:
        // Dispatcher.CurrentDispatcher.Invoke(() =>
        await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
        {
            var data = dropInfo.Data as SomeObject;
            var collection = (ObservableCollection<SomeObject>)
                                   ((ICollectionView) dropInfo.TargetCollection).SourceCollection;
            if (data != null)
            {
                // Operate with collection
            }
            else if (dropInfo.Data is IEnumerable<SomeObject>)
            {
                // Operate with collection
            }
        });
    });
    // Never reaches this point
    MainViewModel.Current.CloseDialog();
}

这样,UI 只会冻结,但在工作完成后完成:

public async void Drop(IDropInfo dropInfo)
{
    MainViewModel.Current.ShowBusyDialog();
    await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
    {
        var data = dropInfo.Data as SomeObject;
        var collection = (ObservableCollection<SomeObject>)
                               ((ICollectionView) dropInfo.TargetCollection).SourceCollection;
        if (data != null)
        {
            // Operate with collection
        }
        else if (dropInfo.Data is IEnumerable<SomeObject
        {
            // Operate with collection
        }
    });
    MainViewModel.Current.CloseDialog();
}

错过了什么,或者我怎样才能让它按预期工作?

编辑

首先感谢您的回答和解释,非常有帮助!我现在尝试了这个,collection在方法更新结束时,UI 不会冻结并且对话框显示正确,但集合不会在 UI 中更新,也不会在 ViewModel 中更新。
当我让它直接与集合一起操作(在 UI 线程上(时,它将直接在两者中更新。顺便说一句,Drop 方法在匹配的 ViewModel 中,但由于验证和检查等原因,我只能将绑定的集合作为只读访问。所以我只能通过自定义方法添加/删除项目,那太过分了。

在期待已久的任务中,Resharper说:Implicitly captured closure: collection
在等待的调用:Implicitly captured closure: dropInfo
但这应该没问题,因为操作不会花那么长时间。

public async void Drop(IDropInfo dropInfo)
{
    MainViewModel.Current.ShowBusyDialog();
    var collection = (ObservableCollection<SomeObject>)
                            ((ICollectionView) dropInfo.TargetCollection).SourceCollection;
    if (collection == null)
        return;
    // ObservableCollection needed for .Move() extension
    var collectionCopy = new ObservableCollection<SomeObject>(collection.ToList());
    await Task.Run(() =>
    {
        var data= dropInfo.Data as SomeObject;
        if (data!= null)
        {
            // operate with collectionCopy (Move item)
        }
        else if (dropInfo.Data is IEnumerable<SomeObject>)
        {
            // operate with collectionCopy (Move items)
        }
    });
    var dispatcher = Dispatcher.CurrentDispatcher;
    await dispatcher.InvokeAsync(() =>
    {
        collection = collectionCopy;
        // Just tried this but didn't work
        RaisePropertyChanged(nameof(collection));
    });
    // collection is updated at this point
    MainViewModel.Current.CloseDialog();
}

更新---------------------------------------------------------------------

已创建并上传示例以显示问题:单击

在调度程序上异步运行操作不会完成或冻结 UI

错过了什么,或者我怎样才能让它按预期工作?

正如其他人所指出的,Task.Run将在后台线程上执行工作,Dispatcher.InvokeAsync将在 UI 线程上转身执行工作。因此,您的代码实际上并没有在 UI 线程以外的任何地方执行任何实际工作,这就是它"阻塞"的原因。

最干净的解决方案是在 UI 线程上将所有必要的信息从 UI 对象中复制出来,在线程池上执行任何后台工作,最后(如有必要(将任何结果复制回 UI 对象。

public async void Drop(IDropInfo dropInfo)
{
  MainViewModel.Current.ShowBusyDialog();
  // First, copy the data out of the UI objects.
  List<SomeObject> list;
  var data = dropInfo.Data as SomeObject;
  var collection = (ObservableCollection<SomeObject>)
                               ((ICollectionView) dropInfo.TargetCollection).SourceCollection;
  if (collection != null)
  {
    list = collection.ToList();
  }
  else if (dropInfo.Data is IEnumerable<SomeObject>)
  {
    list = ((IEnumerable<SomeObject>)dropInfo.Data).ToList();
  }
  // Then do the background work.
  await Task.Run(() =>
  {
    // Operate with `list`
  });
  // Finally, update the UI objects after the work is complete.
  MainViewModel.Current.CloseDialog();
}

您正在 UI 调度程序上运行所有这些代码

await Dispatcher.CurrentDispatcher.InvokeAsync(() =>

你需要做的是...

  1. 在这里运行您的所有工作await Task.Run(() =>
  2. 完成后,使用调度程序更新 UI。

顺便说一下,如果要更新实现 INotifyPropertyChanged 的绑定属性,则甚至不必使用调度程序。 从任何线程更新属性。 绑定将自动将更新编送到 UI 线程上。

在代码中使用的调度程序是 UI 调度程序。 这意味着,当您将方法调度到该方法上时,您将在 UI 线程上执行该方法。 仅在最后一刻使用调度程序,并且仅将其用于

  • 从其他线程更新 UI
  • 在应用程序生命周期事件之后安排 UI 工作(请参阅调度程序优先级枚举(

还有一个问题...

Dispatcher.CurrentDispatcher检索...当前调度程序。 当前调度程序是什么? 它是当前线程的调度程序。 在第一个示例中,当前线程是后台线程。 您希望从 UI 使用调度程序。 这是第一个示例,但经过调整...

public async void Drop(IDropInfo dropInfo)
{
    // We are in the UI thread here!
    MainViewModel.Current.ShowBusyDialog();
    // as we are on the UI thread, no worries touching the collection here
    var collection = (ObservableCollection<SomeObject>)
         ((ICollectionView)dropInfo.TargetCollection).SourceCollection;
    // as we are on the UI thread, this is the UI dispatcher.
    var dispatcher = Dispatcher.CurrentDispatcher;
    await Task.Run(() =>
    {
        // We are on a background thread here!
        var data = dropInfo.Data as SomeObject;
        YourResults results = null;
        if (data != null)
        {
            results = WhateverProcessingTakesALongTime();
        }
        else if (dropInfo.Data is IEnumerable<SomeObject>)
        {
            results = SameHereIHaveNoIdea(dropInfo.Data);
        }
        // Now, let's update the UI on the UI thread.
        await dispatcher.InvokeAsync(() =>
        {
            UpdateStuffWithStuff(collection, results);
        });
    });
    MainViewModel.Current.CloseDialog();
}