2011-02-12 125 views
0

我有一個XAML應用程序,用作自動化的UI。整個自動化可能需要20-30小時的時間才能完全執行,因此我創建了一個基本上包裝線程方法(啓動/停止/重置)的任務類對象。WPF/XAML:如何執行線程進程並防止主UI忙/凍結?

但是,當我在Task對象下運行自動化方法時,XAML UI很忙,我無法與其他控件交互,包括切換Thread.Set()標誌的Pause按鈕。

還有另一篇文章 Prevent UI from freezing without additional threads

,其中有人推薦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)); 
    } 
} 
+0

請注意 - 我建議不要創建一個名爲Task的類,因爲它與框架的Task類衝突:http://msdn.microsoft.com/en-us/library/system.threading。 tasks.task.aspx – 2011-02-12 02:37:19

回答

3

,其中有人推薦BackgroundWorker的類此MSDN文章提到這是一個壞主意,用這個,如果它操縱的UI,這礦的確顯示狀態的目的,對象,當計數

BackgroundWorker實際上是理想的選擇,因爲它是爲這種類型的場景設計的。警告的是,您不應該更改DoWork中的UI元素,而應該通過ReportProgressProgressChanged事件更改UI元素。

警告之所以存在,是「DoWork的」是在後臺線程上執行。如果你從那裏設置一個UI元素值,你會得到一個交叉線程異常。但是,ReportProgress/ProgressChanged會自動將呼叫編組爲適合您的SynchronizationContext

1

查看WPF中的Dispatcher對象。您可以並且應該在您的場景中,在後臺線程上運行長時間運行的任務,並且BackgroundWorker是一種很好的方法。當您需要更新UI時,您需要驗證對UI線程的訪問權限,如果您沒有,則使用調度程序在UI線程上調用更新方法。

1

這裏有兩種可能的原因:第一,阻塞任務阻塞了UI線程而不是在後臺線程上運行;第二,後臺線程正在匱乏UI線程,以至於它永遠沒有機會迴應輸入。你需要找出哪些是這種情況。一個簡單的方法是在你的Click處理程序中,Debug.WriteLine當前的線程ID(Thread.CurrentThread.ManagedThreadId),並在RunFullAutomation回調中執行相同的操作。

如果這些打印相同的號碼,那麼你有第一個問題。裏德和TheZenker爲此提供瞭解決方案。

如果這些打印不同的號碼,那麼你已經在工作線程,和你有第二個問題。 (BackgroundWorker可能會更優雅地讓你進入工作線程,並會幫助更新UI,但它不會停止飢餓。)在這種情況下,最簡單的修復可能是在啓動工作線程之前設置_thread.Priority = ThreadPriority.BelowNormal;

順便說一句,你的代碼永遠不會出現實際調用AutomationThread.Start,這意味着RunFullAutomation回調甚至沒有執行。這只是一個錯字嗎?

+0

是的,這是一個錯字。感謝您指出了這一點! – Wibble 2011-02-17 17:55:34

1

因爲.NET 4完全支持在後臺使用任務並行庫異步運行任務,所以建議不要推出自己的Task類 也就是說,您可以執行Reed建議的操作,並使用BackgroundWorker是理想的,或者如果你在任務SI如何執行的性質更喜歡更多的控制,你可以從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併發布消息以更新您需要的用戶界面。

該示例具有一個進度條和一個標籤控件,該標籤控件在for循環的每次迭代中都會更新。使用CancellationTokenSource來支持取消,每次迭代都會檢查它。

希望這會有所幫助。