[英]WPF / XAML: How do I execute threaded processes and prevent the main UI from being busy / freezing?
我有一個XAML應用程序,可以用作自動化的UI。 整個自動化過程可能需要20到30個小時才能完全執行,因此我創建了一個Task類對象,該對象實際上包裝了Thread方法(Start / Stop / Reset)。
但是,當我在Task對象下運行自動化方法時,XAML UI繁忙,並且我無法與其他控件進行交互,包括用於切換Thread.Set()標志的“暫停”按鈕。
還有另一篇文章, 在沒有其他線程的情況下防止UI凍結
當有人在MSDN文章中推薦的地方推薦BackgroundWorker類時,如果它在UI中操作對象時使用此方法是一個壞主意,我的這樣做是為了顯示狀態計數: http : //msdn.microsoft.com/zh-cn /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));
}
}
在MSDN文章中有人建議使用BackgroundWorker類的地方,如果它在UI中操作對象,則使用此方法是一個壞主意,我的這樣做是為了顯示狀態計數
實際上, BackgroundWorker對此非常理想,因為它是針對此類情況而設計的。 警告是您不應在DoWork
內部更改UI元素,而應通過ReportProgress
和ProgressChanged
事件。
該警告存在的原因是在后台線程上執行了“ DoWork”。 如果從此處設置UI元素值,則會得到交叉線程異常。 但是,ReportProgress / ProgressChanged會自動將調用封送回給您合適的SynchronizationContext
。
看一下WPF中的Dispatcher對象。 您可以並且應該在您的方案中在后台線程上運行長時間運行的任務,而BackgroundWorker是執行此任務的好方法。 當您需要更新UI時,需要驗證對UI線程的訪問,如果沒有,請使用分派器在UI線程上調用更新方法。
這里有兩個可能的原因:首先,阻塞任務正在阻塞UI線程,而不是在后台線程上運行;其次,后台線程正在使UI線程處於飢餓狀態,因此它永遠沒有機會響應輸入。 您需要找出其中的哪種情況。 一種簡單的方法是,在您的Click處理程序中,使用Debug.WriteLine當前線程ID(Thread.CurrentThread.ManagedThreadId),然后在RunFullAutomation回調中執行相同的操作。
如果這些打印相同的數字,那么您將遇到第一個問題。 Reed和TheZenker已為此提供了解決方案。
如果這些命令打印出不同的數字,則說明您已經在工作線程中,並且還有第二個問題。 (BackgroundWorker可能會讓您更優雅地進入工作線程,並有助於更新UI,但不會停止飢餓。)在這種情況下,最簡單的解決方法可能是設置_thread.Priority = ThreadPriority.BelowNormal;
在啟動工作線程之前。
順便說一句,您的代碼似乎永遠不會真正調用AutomationThread.Start
,這意味着甚至沒有執行RunFullAutomation回調。 這只是錯字嗎?
我建議不要發布自己的Task類,因為.NET 4完全支持使用Task Parallel Library在后台異步運行任務。也就是說,您可以執行Reed的建議並使用理想的BackgroundWorker或希望更好地控制任務執行方式的性質,您可以使用System.Threading.Tasks中的Task類並實現類似以下內容:
public partial class MainWindow : Window
{
CancellationTokenSource source = new CancellationTokenSource();
SynchronizationContext context = SynchronizationContext.Current;
Task task;
public MainWindow()
{
InitializeComponent();
}
private void DoWork()
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(500); //simulate long running task
if (source.IsCancellationRequested)
{
context.Send((_) => labelPrg.Content = "Cancelled!!!", null);
break;
}
context.Send((_) => labelPrg.Content = prg.Value = prg.Value + 1, null);
}
}
private void Start_Click(object sender, RoutedEventArgs e)
{
task = Task.Factory.StartNew(DoWork, source.Token);
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
source.Cancel();
}
}
在DoWork()
您使用WPF SynchronizationContext並發布消息以更新所需的UI wiget。
該示例具有進度條和標簽控件,該標簽控件在for循環的每次迭代中都會更新。使用CancellationTokenSource
支持CancellationTokenSource
,該CancellationTokenSource
在每次迭代中都會進行檢查。
希望這可以幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.