在Dispatcher上异步运行Action将无法完成或冻结UI

时间:2016-08-02 14:21:54

标签: c# wpf asynchronous mvvm drag-and-drop

我只想异步运行Drop动作以在移动大量时显示忙碌的Dialog。因为源集合只能由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不会冻结,Dialog会正确显示,但是在ViewModel中也不会在UI中更新集合。 /> 当我让它直接使用集合(在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();
}

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

创建并上传示例以显示问题:Click

2 个答案:

答案 0 :(得分:1)

  

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

正如其他人所说,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();
}

答案 1 :(得分:0)

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

filter

你需要做的是......

  1. 在此处await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
  2. 运行您的所有工作
  3. 完成后,使用调度程序更新UI。
  4. 顺便说一下,如果您要更新实现INotifyPropertyChanged的绑定属性,您甚至不必使用调度程序。从任何线程更新属性。 Binding将自动将更新编组到UI线程上。

    您在代码中使用的Dispatcher是 UI Dispatcher 。这意味着,当您将方法分派到它时,您正在UI线程上执行该方法。只能在最后一刻使用Dispatcher,并且只能将其用于

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

    还有一个问题......

    await Task.Run(() =>检索...当前调度程序。目前的调度员是什么?它是当前线程的调度程序。在第一个示例中,当前线程是后台线程。您想要使用UI中的调度程序。这是第一个例子,但调整了......

    Dispatcher.CurrentDispatcher
相关问题