WPF/C# 不要阻塞 UI
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13934697/
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
WPF/C# Don't block the UI
提问by J4N
I've an existing WPF application, which has several sections. Every section is a UserControl, that implements an interface.
我有一个现有的 WPF 应用程序,它有几个部分。每个部分都是一个 UserControl,它实现了一个接口。
The interface specify two methods: void LoadData([...])and bool UnloadData().
该接口指定了两种方法:void LoadData([...])和bool UnloadData()。
Those method are called by the UI thread, so we need to do our work in backgroundworker if it's time consuming.
这些方法是由UI线程调用的,所以如果很耗时,我们需要在backgroundworker中完成我们的工作。
No problems with LoadDatasince we can update the UI asynchronously. The problem is with UnloadData().
没有问题,LoadData因为我们可以异步更新 UI。问题在于 UnloadData()。
This should return if we can really leave the current view.
如果我们真的可以离开当前视图,这应该返回。
This is computed with the current status of data(Saved/modified/Invalid):
这是根据数据的当前状态(已保存/修改/无效)计算的:
- Saved return true,
- Invalid asks if you want to stay to save some correct data or leave without saving
- Modified tell you that you can either cancel your change(return true), either continue to edit(return false), either save you current data(return true)
- 保存返回真,
- Invalid 询问您是要留下来保存一些正确的数据还是不保存就离开
- 修改后告诉你,你可以取消你的更改(返回真),或者继续编辑(返回假),或者保存你当前的数据(返回真)
The problem is with the "Modified -> Save". This is a time consuming method, so to respect the philosophy of the application, we should run this in a background thread(with a busy indicator).
问题在于“修改 - > 保存”。这是一种耗时的方法,因此为了尊重应用程序的理念,我们应该在后台线程中运行它(带有忙碌指示器)。
But if we just launch the thread and go to the next section, it will return "true" to the method call, and we will directly launch the next view.
但是如果我们只是启动线程并进入下一节,它会向方法调用返回“true”,我们将直接启动下一个视图。
In my case, loading the next view before our local data is saved can be a problem.
就我而言,在保存本地数据之前加载下一个视图可能是个问题。
So:
所以:
Is there a way to wait on the background thread to finish before returning "true", WITHOUT blocking the UI?
有没有办法在返回“true”之前等待后台线程完成而不阻塞 UI?
public bool UnloadData(){
if(...){
LaunchMyTimeConsumingMethodWithBackgroundWorker();
return true;//Only when my time consuming method ends
}
//[...]
}
Important EDITMaybe I wasn't clear enought: I know how to use a BackgroundWorker, or TPL. My problem is that the parent class(the one which call the UnloadData()" is a class that I cannot edit(for multiple reasons: It's in another DLL that will not be reloaded, it already works with 70+ userControls, all in separate projects(dll), loaded by reflection.
重要编辑也许我不够清楚:我知道如何使用 BackgroundWorker 或 TPL。我的问题是父类(调用 UnloadData() 的那个类是一个我无法编辑的类(出于多种原因:它在另一个不会重新加载的 DLL 中,它已经与 70 多个用户控件一起使用,全部分开项目(dll),通过反射加载。
This wasn't my choice, I don't find it good, but I've to deal with it now. I'm mostly looking for way to make my method wait on the return of my method. I'm not sure if it is possible. But I'm looking for a workaround, it will spare me weeks of works.
这不是我的选择,我觉得它不好,但我现在必须处理它。我主要是在寻找让我的方法等待我的方法返回的方法。我不确定这是否可能。但我正在寻找一种解决方法,它可以让我节省数周的工作时间。
回答by AkselK
Ok now I'm excited, because I think I may have discovered something on my own...
好的,现在我很兴奋,因为我想我可能自己发现了一些东西......
So, what you do is this: You create a DispatcherFrame, push that frame onto the Dispatcher, and in the RunWorkerCompleted you set the Continue of the Frame to false.
因此,您要做的是:创建一个 DispatcherFrame,将该帧推送到 Dispatcher,然后在 RunWorkerCompleted 中将帧的继续设置为 false。
This is the code so far:
这是到目前为止的代码:
public void Function()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += TimeConsumingFunction;
var frame = new DispatcherFrame();
worker.RunWorkerCompleted += (sender, args) =>
{
frame.Continue = false;
};
worker.RunWorkerAsync();
Dispatcher.PushFrame(frame);
}
private void TimeConsumingFunction(object sender, DoWorkEventArgs doWorkEventArgs)
{
Console.WriteLine("Entering");
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
}
Console.WriteLine("Exiting");
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Function();
Console.WriteLine("Returns");
}
回答by Andy
You should implement a dependency property "IsBusy" of type bool, that you set to TRUE before starting the BackgoundWorker, and then to FALSE when the work is complete.
您应该实现 bool 类型的依赖属性“IsBusy”,在启动 BackgoundWorker 之前将其设置为 TRUE,然后在工作完成后设置为 FALSE。
On the UI, you bind to that property whatever functionality you want disabled during the processing(like the button for loading the next view, etc.); or maybe showing a "Cancel" button.
在 UI 上,您将在处理过程中要禁用的任何功能(例如用于加载下一个视图的按钮等)绑定到该属性;或者可能显示“取消”按钮。
You should not "wait" for the operation to complete, you can retrieve the result in an additional variable, that the BackgroundWorker will set:
您不应该“等待”操作完成,您可以在一个附加变量中检索结果,BackgroundWorker 将设置该变量:
BackgroundWorker _bw;
bool _returnValue = false;
private void button_Click(object sender, RoutedEventArgs e)
{ // if starting the processing by clicking a button
_bw = new BackgroundWorker();
IsBusy = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted);
_bw.RunWorkerAsync();
}
void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IsBusy = false;
// retrieve the result of the operation in the _returnValue variable
}
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
_returnValue = UnloadData();
}
private bool UnloadData()
{
if (...)
{
LaunchTimeConsumingMethod();
return true;
}
else
return false;
//etc ...
}
public bool IsBusy
{
get { return (bool)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
// Using a DependencyProperty as the backing store for IsBusy. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsBusyProperty =
DependencyProperty.Register( ... )
回答by BTownTKD
You may be able to try using the new "await" features of .NET 4.5.
您可以尝试使用.NET 4.5的新“等待”功能。
The await keyword allows you to await the completion of a Taskobject, without blocking the UI.
await 关键字允许您等待Task对象的完成,而不会阻塞 UI。
Try this modification:
试试这个修改:
public async bool UnloadData()
{
if(...)
{
await Task.Factory.StartNew(() =>
{
LaunchMyTimeConsumingMethod();
});
return true;//Only when my time consuming method ends
}
//[...]
}
回答by Arthur Nunes
Treat UnloadData as a async operation and let the async/await features handle both the case when it completes synchronously and when it needs to complete asynchronously:
将 UnloadData 视为异步操作,并让 async/await 功能处理同步完成和需要异步完成的情况:
public async Task<bool> UnloadData(){
if(...){
// The await keyword will segment your method execution and post the continuation in the UI thread
// The Task.Factory.StartNew will run the time consuming method in the ThreadPool
await Task.Factory.StartNew(()=>LaunchMyTimeConsumingMethodWithBackgroundWorker());
// The return statement is the continuation and will run in the UI thread after the consuming method is executed
return true;
}
// If it came down this path, the execution is synchronous and is completely run in the UI thread
return false;
}
private async void button_Click(object sender, RoutedEventArgs e)
{
// Put here your logic to prevent user interaction during the operation's execution.
// Ex: this.mainPanel.IsEnabled = false;
// Or: this.modalPanel.Visibility = Visible;
// etc
try
{
bool result = await this.UnloadData();
// Do whatever with the result
}
finally
{
// Reenable the user interaction
// Ex: this.mainPanel.IsEnabled = true;
}
}
EDIT
编辑
If you can't modify the UnloadData, then just execute it on the ThreadPool, as @BTownTKD noted:
如果您无法修改 UnloadData,则只需在 ThreadPool 上执行它,正如@BTownTKD 所指出的:
private async void button_Click(object sender, RoutedEventArgs e)
{
// Put here your logic to prevent user interaction during the operation's execution.
// Ex: this.mainPanel.IsEnabled = false;
// Or: this.modalPanel.Visibility = Visible;
// etc
try
{
// The await keyword will segment your method execution and post the continuation in the UI thread
// The Task.Factory.StartNew will run the time consuming method in the ThreadPool, whether it takes the long or the short path
bool result = await The Task.Factory.StartNew(()=>this.UnloadData());
// Do whatever with the result
}
finally
{
// Reenable the user interaction
// Ex: this.mainPanel.IsEnabled = true;
}
}
回答by Eugene Cheverda
You probably should use TPL if your framework version is 4.0:
如果您的框架版本是 4.0,您可能应该使用 TPL:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // this will work only if you're running this code from UI thread, for example, by clicking a button
Task.Factory.StartNew(() => UnloadData()).ContinueWith(t => /*update ui using t.Result here*/, uiScheduler);
Hope this helps.
希望这可以帮助。
回答by Chris McCabe
You have to implement a callback function (RunWorkerCompleted), this is called when the background worker finishes.
您必须实现一个回调函数 (RunWorkerCompleted),它会在后台工作完成时调用。
Check out an example here: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
在此处查看示例:http: //msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

