在调度程序上异步运行操作不会完成或冻结 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();
}
更新---------------------------------------------------------------------
已创建并上传示例以显示问题:单击
我错过了什么,或者我怎样才能让它按预期工作?
正如其他人所指出的,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(() =>
你需要做的是...
- 在这里运行您的所有工作
await Task.Run(() =>
- 完成后,使用调度程序更新 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();
}