WPF / XAML:如何执行线程进程并防止主 UI 繁忙/冻结?
我有一个 XAML 应用程序,用作自动化的 UI。整个自动化可能需要 20-30 小时才能完全执行,因此我创建了一个 Task 类对象,它本质上包装了 Thread 方法(启动/停止/重置)。
但是,当我在 Task 对象下运行自动化方法时,XAML UI 很忙,我无法与其他控件交互,包括切换 Thread.Set() 标志的“暂停”按钮。
还有一个帖子 防止 UI 在没有额外线程的情况下冻结
,有人推荐使用 BackgroundWorker 类MSDN 文章提到,当它操作 UI 中的对象时,使用它是一个坏主意,我这样做是为了显示状态计数: http://msdn.microsoft.com/en-us/ Library/system.componentmodel.backgroundworker.aspx
对此有什么想法吗?
private void OnButtonStartAutomationClick(object sender, RoutedEventArgs e)
{
btnPauseAutomation.IsEnabled = true;
Automation.Task AutomationThread = new Automation.Task(RunFullAutomation);
}
private void RunFullAutomation()
{
// do stuff that can take 20+ hours
// threaded so I can utilize a pause button (block)
}
class Task
{
private ManualResetEvent _shutdownFlag = new ManualResetEvent(false);
private ManualResetEvent _pauseFlag = new ManualResetEvent(true);
private Thread _thread;
private readonly Action _action;
public Task(Action action)
{
_action = action;
}
public void Start()
{
ThreadStart ts = new ThreadStart(DoDelegatedMethod);
_thread = new Thread(ts);
_thread.Start();
_thread.Priority = ThreadPriority.Lowest;
}
public void Resume()
{
_pauseFlag.Set();
}
public void Stop()
{
_shutdownFlag.Set();
_pauseFlag.Set();
_thread.Join();
}
private void DoDelegatedMethod()
{
do
{
_action();
}
while (!_shutdownFlag.WaitOne(0));
}
}
I have a XAML application that serves as the UI for an automation. The entire automation can take anywhere from 20-30 hours to fully execute so I created a Task class object that essentially wraps Thread methods (Start/Stop/Reset).
However, when I run the automation method under the Task object, the XAML UI is busy and I cannot interact with the other controls, including the Pause button which toggles the Thread.Set() flag.
There is another post
Prevent UI from freezing without additional threads
where someone recommended the BackgroundWorker class this MSDN article mentions it is a bad idea to use this when if it manipulates objects in the UI, which mine does for purposes of displaying status counts:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Any idea around this?
private void OnButtonStartAutomationClick(object sender, RoutedEventArgs e)
{
btnPauseAutomation.IsEnabled = true;
Automation.Task AutomationThread = new Automation.Task(RunFullAutomation);
}
private void RunFullAutomation()
{
// do stuff that can take 20+ hours
// threaded so I can utilize a pause button (block)
}
class Task
{
private ManualResetEvent _shutdownFlag = new ManualResetEvent(false);
private ManualResetEvent _pauseFlag = new ManualResetEvent(true);
private Thread _thread;
private readonly Action _action;
public Task(Action action)
{
_action = action;
}
public void Start()
{
ThreadStart ts = new ThreadStart(DoDelegatedMethod);
_thread = new Thread(ts);
_thread.Start();
_thread.Priority = ThreadPriority.Lowest;
}
public void Resume()
{
_pauseFlag.Set();
}
public void Stop()
{
_shutdownFlag.Set();
_pauseFlag.Set();
_thread.Join();
}
private void DoDelegatedMethod()
{
do
{
_action();
}
while (!_shutdownFlag.WaitOne(0));
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
BackgroundWorker 实际上非常适合这种情况,因为它是为这种类型的场景而设计的。警告是您不应更改
DoWork
内部的 UI 元素,而应通过ReportProgress
和ProgressChanged
事件更改。警告存在的原因是“DoWork”是在后台线程上执行的。如果您从那里设置 UI 元素值,您将收到跨线程异常。但是,ReportProgress/ProgressChanged 会自动将调用封送回适合您的
SynchronizationContext
中。BackgroundWorker is actually ideal for this, as it was designed for this type of scenario. The warning is that you shouldn't change UI elements inside of
DoWork
, but rather viaReportProgress
and theProgressChanged
event.The reason the warning exists is "DoWork" is executed on a background thread. If you set a UI element value from there, you'll get a cross threading exception. However, ReportProgress/ProgressChanged automatically marshals the call back into the proper
SynchronizationContext
for you.看一下 WPF 中的 Dispatcher 对象。您可以而且应该在您的场景中在后台线程上运行长时间运行的任务,而 BackgroundWorker 是一个很好的方法。当您需要更新 UI 时,您需要验证对 UI 线程的访问,如果没有,请使用调度程序在 UI 线程上调用更新方法。
Take a look at the Dispatcher object in WPF. You can, and should in your scenario, run the long running tasks on a background thread and the BackgroundWorker is a good way to do it. When you need to update the UI you need to verify access to the UI thread and if you don't have it use the dispatcher to invoke an update method on the UI thread.
这里有两个可能的原因:第一,阻塞任务正在阻塞 UI 线程,而不是在后台线程上运行;第二,后台线程使 UI 线程处于饥饿状态,使其永远没有机会响应输入。您需要找出属于哪一种情况。执行此操作的一种粗略方法是,在 Click 处理程序中,使用 Debug.WriteLine 当前线程 ID (Thread.CurrentThread.ManagedThreadId),并在 RunFullAutomation 回调中执行相同的操作。
如果它们打印相同的数字,那么您遇到了第一个问题。 Reed 和 TheZenker 对此提供了解决方案。
如果它们打印不同的数字,那么您已经在工作线程上,并且遇到了第二个问题。 (BackgroundWorker 可能会让您更优雅地进入工作线程,并有助于更新 UI,但它不会停止饥饿。)在这种情况下,最简单的修复可能是设置
_thread.Priority = ThreadPriority.BelowNormal;
在启动工作线程之前。顺便说一句,您的代码似乎从未真正调用
AutomationThread.Start
,这意味着 RunFullAutomation 回调甚至没有执行。这只是一个错字吗?There are two possible causes here: first, that the blocking task is blocking the UI thread rather than running on a background thread, and second, that the background thread is starving the UI thread so that it never gets the chance to respond to input. You need to find out which of these is the case. A crude way to do this is, in your Click handler, Debug.WriteLine the current thread ID (Thread.CurrentThread.ManagedThreadId), and do the same in the RunFullAutomation callback.
If these print the same number, then you have the first problem. Reed and TheZenker have provided solutions to this.
If these print different numbers, then you are already on a worker thread, and you have the second problem. (BackgroundWorker may get you to the worker thread more elegantly, and will help with updating the UI, but it won't stop starvation.) In this case the simplest fix is probably to set
_thread.Priority = ThreadPriority.BelowNormal;
before starting the worker thread.By the way, your code never appears to actually call
AutomationThread.Start
, which means the RunFullAutomation callback isn't even executed. Is this just a typo?我建议不要推出自己的 Task 类,因为 .NET 4 完全支持使用任务并行库在后台异步运行任务
也就是说,您可以按照 Reed 的建议并使用理想的 BackgroundWorker,或者如果您更喜欢对任务执行方式的性质进行更多控制,您可以使用 System.Threading.Tasks 中的 Task 类并实现如下所示的内容:
在
DoWork()
中,您使用 WPF SynchronizationContext 并发布消息来更新您需要的 UI wiget。该示例有一个进度条和一个标签控件,该控件在 for 循环的每次迭代中更新。使用在每次迭代中检查的
CancellationTokenSource
支持取消。希望这有帮助。
I'd advise against rolling out your own Task class given that .NET 4 has full support for running tasks asynchronously in the background using the Task Parallel Library
That said,you can do what Reed suggests and use a BackgroundWorker which is ideal or if you prefer more control over the nature of how the task si executing, you could use the Task class from System.Threading.Tasks and implement something like so:
In
DoWork()
you use the WPF SynchronizationContext and post messages to update the UI wiget you need.The example has a progress bar and a label control that is updated on each iteration of the for loop.Cancellation is supported using
CancellationTokenSource
which is checked in each iteration.Hope this helps.