我只想异步运行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
答案 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
你需要做的是......
await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
顺便说一下,如果您要更新实现INotifyPropertyChanged的绑定属性,您甚至不必使用调度程序。从任何线程更新属性。 Binding将自动将更新编组到UI线程上。
您在代码中使用的Dispatcher是 UI Dispatcher 。这意味着,当您将方法分派到它时,您正在UI线程上执行该方法。只能在最后一刻使用Dispatcher,并且只能将其用于
还有一个问题......
await Task.Run(() =>
检索...当前调度程序。目前的调度员是什么?它是当前线程的调度程序。在第一个示例中,当前线程是后台线程。您想要使用UI中的调度程序。这是第一个例子,但调整了......
Dispatcher.CurrentDispatcher