在WPF中,Suspend/ResumeLayout()和Windows Forms中的BackgroundWorker()有什么等效的替代方法?

15

如果我在代码后台的一个函数中,想要实现在状态栏中显示“正在加载...”的功能,以下代码看起来很合理,但我们从WinForms了解到,这是不可取的:

StatusBarMessageText.Text = "Loading Configuration Settings...";            
LoadSettingsGridData();
StatusBarMessageText.Text = "Done";

从WinForms第1章的101课中我们都知道,除非整个函数执行完毕,否则窗体不会向用户显示更改...这意味着“加载”消息永远不会被显示给用户。需要使用以下代码。

Form1.SuspendLayout();    
StatusBarMessageText.Text = "Loading Configuration Settings...";                
Form1.ResumeLayout();

LoadSettingsGridData();

Form1.SuspendLayout();    
StatusBarMessageText.Text = "Done";
Form1.ResumeLayout();

在WPF中处理这个基本问题的最佳实践是什么?

3个回答

37

最佳和最简单的方法:

using(var d = Dispatcher.DisableProcessing())
{
    /* your work... Use dispacher.begininvoke... */
}
或者
IDisposable d;

try
{
    d = Dispatcher.DisableProcessing();
    /* your work... Use dispacher.begininvoke... */
} finally {
    d.Dispose();
}

1
我刚刚将这个添加到了.NET 4应用程序中的DataGrid更新中,它使得网格更新更加流畅。 - yzorg
15
你肯定希望将这个代码放进 using (var d = Dispatcher.DisableProcessing()) 里面,这样任何异常都不会使其永久被禁用。 - Roman Starkov
2
有些危险,因为它会暂停 Dispatcher 而不仅仅是单个窗口,所以只要使用同一个 Dispatcher 运行的所有 UI 操作都将被停止。(在大多数情况下都是这样) - Kelly

0

让这个工作起来最简单的方法是将LoadSettingsGridData添加到调度程序队列中。如果您将操作的DispatcherPriority设置得足够低,布局操作将会发生,然后您就可以继续进行了。

StatusBarMessageText.Text = "Loading Configuration Settings...";
this.Dispatcher.BeginInvoke(new Action(LoadSettingsGridData), DispatcherPriority.Render);
this.Dispatcher.BeginInvoke(new Action(() => StatusBarMessageText.Text = "Done"), DispatcherPriority.Render);

0
在阅读 Shawn Wildermuth 的文章《WPF Threads: Build More Responsive Apps With The Dispatcher》时,我发现以下内容,它表示你可以像在 WindowsForms 中一样使用 Background Worker。真是太神奇了:

BackgroundWorker Now that you have a sense of how the Dispatcher works, you might be surprised to know that you will not find use for it in most cases. In Windows Forms 2.0, Microsoft introduced a class for non-UI thread handling to simplify the development model for user interface developers. This class is called the BackgroundWorker. Figure 7 shows typical usage of the BackgroundWorker class.

Figure 7 Using a BackgroundWorker in WPF

BackgroundWorker _backgroundWorker = new BackgroundWorker();

...

// Set up the Background Worker Events
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += 
    _backgroundWorker_RunWorkerCompleted;

// Run the Background Worker
_backgroundWorker.RunWorkerAsync(5000);

...

// Worker Method
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Do something
}

// Completed Method
void _backgroundWorker_RunWorkerCompleted(
    object sender, 
    RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        statusText.Text = "Cancelled";
    }
    else if (e.Error != null) 
    {
        statusText.Text = "Exception Thrown";
    }
    else 
    {
        statusText.Text = "Completed";
    }
}

The BackgroundWorker component works well with WPF because underneath the covers it uses the AsyncOperationManager class, which in turn uses the SynchronizationContext class to deal with synchronization. In Windows Forms, the AsyncOperationManager hands off a WindowsFormsSynchronizationContext class that derives from the SynchronizationContext class. Likewise, in ASP.NET it works with a different derivation of SynchronizationContext called AspNetSynchronizationContext. These SynchronizationContext-derived classes know how to handle the cross-thread synchronization of method invocation.

In WPF, this model is extended with a DispatcherSynchronizationContext class. By using BackgroundWorker, the Dispatcher is being employed automatically to invoke cross-thread method calls. The good news is that since you are probably already familiar with this common pattern, you can continue using BackgroundWorker in your new WPF projects.


没错,但在这种情况下不需要使用BackgroundWorker(线程)。 - Martin Konicek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接