wpf 如何在不阻塞 UI 线程的情况下继续执行多个任务?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18091780/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I continue after multiple Tasks without blocking the UI thread?
提问by BenCr
In my MVVM application my view model calls 3 different service methods, converts the data from each into a common format and then updates the UI using property notification/observable collections etc.
在我的 MVVM 应用程序中,我的视图模型调用 3 个不同的服务方法,将每个方法的数据转换为通用格式,然后使用属性通知/可观察集合等更新 UI。
Each method in the service layer starts a new Taskand returns the Taskto the view model. Here's an example of one of my service methods.
服务层中的每个方法都会启动一个新的Task并返回Task给视图模型。这是我的一种服务方法的示例。
public class ResourceService
{
internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
var t = Task.Factory.StartNew(() =>
{
//... get resources from somewhere
return resources;
});
t.ContinueWith(task =>
{
if (task.IsFaulted)
{
errorCallback(task.Exception);
return;
}
completedCallback(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
return t;
}
}
Here's the calling code and other relevant parts of the view model...
这是视图模型的调用代码和其他相关部分......
private ObservableCollection<DataItem> Data = new ObservableCollection<DataItem>();
public ICollectionView DataView
{
get { return _dataView; }
set
{
if (_dataView != value)
{
_dataView = value;
RaisePropertyChange(() => DataView);
}
}
}
private void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.WaitAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
private Task LoadResources()
{
return ResourceService.LoadResources(resources =>
{
foreach(var r in resources)
{
var d = convertResource(r);
Data.Add(d);
}
},
error =>
{
// do some error handling
});
}
This almost works but there are a couple of small issues.
这几乎有效,但有几个小问题。
Number 1: In the call to SetBusyat the very beginning, before I start any tasks and before I call WaitAll, I set the IsBusyproperty to true. This should update the UI and show the BusyIndicator control but it's not working. I've also tried adding simple string properties and binding those and they're not being updated either. The IsBusy functionality is part of a base class and works in other view models where I don't have more than one Task running so I don't believe there is an issue with the property notification or data binding in the XAML.
编号 1:在SetBusy最开始的调用中,在我开始任何任务之前和调用之前WaitAll,我将IsBusy属性设置为 true。这应该会更新 UI 并显示 BusyIndicator 控件,但它不起作用。我还尝试添加简单的字符串属性并绑定它们,但它们也没有更新。IsBusy 功能是基类的一部分,可在其他视图模型中运行,在这些视图模型中我没有运行多个任务,因此我不认为 XAML 中的属性通知或数据绑定存在问题。
All the data bindings seem to be updated after the whole method has completed. I'm not seeing any "first time exceptions" or binding errors in the output Window which is leading me to believe the UI thread is somehow being blocked before the call to WaitAll.
在整个方法完成后,所有数据绑定似乎都会更新。我在输出窗口中没有看到任何“第一次异常”或绑定错误,这让我相信 UI 线程在调用 WaitAll 之前以某种方式被阻止。
Number 2: I seem to be returning the wrong Tasks from the service methods. I want everything after WaitAllto run after the view model has converted all the results from all the service methods in the callbacks. However if I return the continuation task from the service method the continuation never gets called and WaitAllwaits forever. The strange thing is the UI control bound to the ICollectionView actually displays everything correctly, I assumed this is because Data is an observable collection and the CollectionViewSource is aware of the collection changed events.
2:我似乎从服务方法返回了错误的任务。我希望WaitAll在视图模型转换了回调中所有服务方法的所有结果之后运行所有内容。但是,如果我从服务方法返回延续任务,延续永远不会被调用并WaitAll永远等待。奇怪的是绑定到 ICollectionView 的 UI 控件实际上正确显示了所有内容,我认为这是因为 Data 是一个可观察的集合,而 CollectionViewSource 知道集合更改事件。
回答by Reed Copsey
You can use TaskFactory.ContinueWhenAllto build a continuation that runs when the input tasks all complete.
您可以使用TaskFactory.ContinueWhenAll构建一个在输入任务全部完成时运行的延续。
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.Factory.ContinueWhenAll(tasks, t =>
{
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}, CancellationToken.None, TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
Note that this becomes simpler if you use C# 5's await/asyncsyntax:
请注意,如果您使用 C# 5 的await/async语法,这会变得更简单:
private async void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
await Task.WhenAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
However if I return the continuation task from the service method the continuation never gets called and WaitAll waits forever
但是,如果我从服务方法返回延续任务,延续永远不会被调用,WaitAll 永远等待
The problem is that your continuation task requires the UI thread, and you're blocking the UI thread in the WaitAllcall. This creates a deadlock which will not resolve.
问题是您的延续任务需要 UI 线程,而您在WaitAll调用中阻塞了 UI 线程。这会造成无法解决的死锁。
Fixing the above should correct this - you'll want to return the Continuation as the Task, as that's what you need to wait for completion - but by using TaskFactory.ContinueWhenAllyou free up the UI thread so it can process those continuations.
修复上述问题应该可以解决这个问题 - 您需要将 Continuation 作为 Task 返回,因为这是您需要等待完成的东西 - 但是通过使用TaskFactory.ContinueWhenAll您可以释放 UI 线程,以便它可以处理这些 continuation。
Note that this is another thing that gets simplified with C# 5. You can write your other methods as:
请注意,这是使用 C# 5 简化的另一件事。您可以将其他方法编写为:
internal static async Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
try
{
await Task.Run(() =>
{
//... get resources from somewhere
return resources;
});
}
catch (Exception e)
{
errorCallback(task.Exception);
}
completedCallback(task.Result);
}
That being said, it's typically better to write the methods to return a Task<T>instead of providing callbacks, as that simplifies both ends of the usage.
话虽如此,通常最好编写返回 a 的方法Task<T>而不是提供回调,因为这简化了使用的两端。

