2008-12-11 43 views
3

我想在主線程上阻止代碼執行,同時仍然允許顯示UI更改。在C#中,等待mainthread,同時繼續處理UI更新? (.NET 2.0 CF)

我試圖想出我想要做的簡化示例版本;這是我能想到的最好的。顯然,它不表明我想要的行爲,或者我不會發布這個問題。我只是希望它給出了一些代碼上下文來支持我希望解決的問題的糟糕解釋。

在窗體上的按鈕單擊處理我有這樣的:

private void button2_Click(object sender, EventArgs e) 
    { 
     AutoResetEvent autoResetEvent = new AutoResetEvent(false); 

     new Thread(delegate() 
     { 
      // do something that takes a while. 
      Thread.Sleep(1000); 

      // Update UI w/BeginInvoke 
      this.BeginInvoke(new ThreadStart(
       delegate() { 
        this.Text = "Working... 1"; 
        this.Refresh(); 
        Thread.Sleep(1000); // gimme a chance to see the new text 
       })); 

      // do something else that takes a while. 
      Thread.Sleep(1000); 

      // Update UI w/Invoke 
      this.Invoke(new ThreadStart(
       delegate() { 
        this.Text = "Working... 2"; 
        this.Refresh(); 
        Thread.Sleep(1000); // gimme a chance to see the new text 
       })); 

      // do something else that takes a while. 
      Thread.Sleep(1000); 

      autoResetEvent.Set(); 
     }).Start(); 


     // I want the UI to update during this 4 seconds, even though I'm 
     // blocking the mainthread 
     if (autoResetEvent.WaitOne(4000, false)) 
     { 
      this.Text = "Event Signalled"; 
     } 
     else 
     { 
      this.Text = "Event Wait Timeout"; 
     } 
     Thread.Sleep(1000); // gimme a chance to see the new text 
     this.Refresh(); 
    } 

如果我沒有設置對WaitOne的一個timout()的應用程序將死鎖的調用()調用。


至於爲什麼我要做到這一點,我一直在負責移動應用程序做的工作在後臺線程中的一個子系統,但仍然有它阻止用戶的工作流程(主線程)僅有時和僅與該子系統有關的某些類型的工作。

回答

0

我去了一些我還沒見過的東西,那就是使用MessageQueues。

  • MainThread在等待隊列上的下一條消息時阻塞。
  • 後臺線程將不同類型的消息發佈到MessageQueue。
  • 某些消息類型發信號通知MainThread更新UI元素。
  • 當然,有一條消息告訴MainThread停止阻塞和等待消息。

考慮到Windows消息循環已經存在某處,似乎超過了頂部,但它的工作原理。

1

構建您的應用程序,以便主線程僅執行UI更新,並且所有其他工作都通過工作隊列在輔助線程上完成;然後添加一個等待Godot標誌到你的主線程,並用它來保護將項目添加到工作隊列的方法

出於好奇:你爲什麼要這麼做?

+0

就你所說的改變應用程序結構而言,它不完全是我的應用程序;那艘船已經駛了。 – CrashCodes 2008-12-11 20:46:39

+0

至於爲什麼我想這樣做,我的任務是移動應用程序的一個子系統在後臺線程中執行工作,但仍然有時會阻止用戶的工作流程(主線程),並且對於某些類型工作的。 – CrashCodes 2008-12-11 20:49:41

+0

@CrashCodes:太模糊,對不起; '有時'和'某些類型'可能意味着什麼 - 你將如何控制時間和內容? – 2008-12-11 20:52:41

2

您想使用「BackgroundWorker」類,它將爲您解決這個問題的大部分......但如前所述,您還需要構造它,以便主線程正在更新UI而這名工人正在舉重。

2

這是easyer,那麼你可能會想。

建議:當你需要一個線程來執行一些偶爾的工作時,從線程池中獲得它,所以你不會需要奇怪的/容易出錯的回收代碼。

當你想在另一個線程上更新你的UI時,你只需要對錶單的引用並調用Form.Invoke來傳遞你想要主線程執行的UI代碼;這是一個最好的例子,儘快發佈UI線程。

即:

private void button1_Click(object sender, EventArgs e) 
{ 
    // this is the UI thread 

    ThreadPool.QueueUserWorkItem(delegate(object state) 
    { 
     // this is the background thread 
     // get the job done 
     Thread.Sleep(5000); 
     int result = 2 + 2; 

     // next call is to the Invoke method of the form 
     this.Invoke(new Action<int>(delegate(int res) 
     { 
      // this is the UI thread 
      // update it! 
      label1.Text = res.ToString(); 
     }), result); 
    }); 
} 

希望這有助於你:)

編輯:我很抱歉,我沒有看過 「阻止用戶的工作流程」 的一部分。

WindowsForms的目的不是爲了阻止主線程是BAD(它處理來自OS的消息)。

您不必通過凍結窗體來阻止用戶工作流程(這將被視爲「未響應」,通過窗口),阻止用戶工作流程的方法是禁用任何您想要的控件(使用Invoke方法以上如果來自另一個線程),甚至整個表格!

2

'阻塞'主線程的常見活動就像打開消息框或模態對話框一樣。主代碼似乎阻塞在MessageBox或ShowDialog調用。

這些項目的工作方式(和MessageBox只是一個專門的模式對話框)是他們包含自己的消息泵,而他們阻止。

雖然這是一個討厭的黑客攻擊,但您可以通過循環調用Application.DoEvents()在您的應用中執行類似操作,以在用戶完成其他任務的同時保持用戶消息的抽取。你需要小心,因爲各種令人討厭的事情可能會導致像這樣的消息 - 例如有人關閉表單或重新輸入當前的消息處理程序 - 模式對話框通過從啓動它們的表單有效地禁用輸入來避免這種情況。

我的意思是說,BackgroundWorker是一個更好的解決方案,如果你能使它適合。我有時將它與模態「進度對話框」結合起來,給我後臺線程/消息抽取和UI線程的阻塞。

編輯 - 擴大的最後一點:

一種方法我用是有一個「進度表格」類,這需要一個BackgroundWorker對象作爲構造函數的參數,幷包含處理程序的進展和被傳遞給它的後臺工作者的完成事件。

希望完成工作的表單創建後臺工作人員並掛接'工作'事件(現在不記得它所稱的內容),然後創建一個進度對話框,將其傳遞給後臺工作人員。然後模態顯示進度對話框,這意味着它將等待(但是泵送消息),直到進度對話框關閉。

進度表格負責從其OnLoad覆蓋啓動BackgroundWorker,並在看到BackgroundWorker完成時關閉自身。顯然你可以在進度表中添加消息文本,進度條,取消按鈕等等。

0

您可能應該像其他人所建議的那樣重構您的代碼,但根據您要查找的行爲,您可能還想看看在後臺工作線程中使用Thread.Join。 Join實際上允許調用線程處理COM和SendMessage事件,同時等待另一個線程完成。這似乎可能會在危險的情況下,但我實際上已經有一些情況下,這是等待另一個線程完成乾淨的唯一方法。

線程.. ::。加入方法

阻塞調用線程,直到 線程終止,同時繼續 執行標準的COM和SendMessage消息 抽水。

(從http://msdn.microsoft.com/en-us/library/95hbf2ta.aspx

0

我與被建議你使用後臺工作的人同意。它完成繁重的工作,並允許用戶界面繼續。您可以使用後臺工作人員的報告進度來啓動主窗體在後臺執行操作時可以設置爲禁用的時間,並在「特定實例」完成處理後重新啓用。

讓我知道這是否有幫助! JFV

0

如果您可以調整您的代碼,以便在進程開始後設置一個標誌,然後在開始附加操作之前在UI中檢查該標誌,我認爲您可以更容易地編寫該代碼。我會創建一個委託,可以從線程池中的線程或用戶創建的線程調用,以更新UI中的進度。一旦後臺進程完成,切換標誌,現在可以繼續正常的UI操作。唯一需要注意的是,當你更新UI組件時,你必須在它創建的線程上,主/ UI線程上進行。爲了做到這一點,你可以在任何位於該線程上的控件上調用Invoke()方法,並將它傳遞給需要調用它的委託和參數。

下面是一個教程我寫了前段時間有關如何使用Control.Invoke()鏈接:

http://xsdev.net/tutorials/pop3fetcher/

0

只是一個代碼片段:沒有太多的時間抱歉:)

private void StartMyDoSomethingThread() { 
     Thread d = new Thread(new ThreadStart(DoSomething)); 
     d.Start(); 
    } 

    private void DoSomething() { 
     Thread.Sleep(1000); 
     ReportBack("I'm still working"); 
     Thread.Sleep(1000); 
     ReportBack("I'm done"); 
    } 

    private void ReportBack(string p) { 
     if (this.InvokeRequired) { 
      this.Invoke(new Action<string>(ReportBack), new object[] { p }); 
      return; 
     } 
     this.Text = p; 
    } 
0

這是最好派遣的工作,但如果你必須,也許這樣的事情。只需調用此方法等待信號,而不是調用waitone。

private static TimeSpan InfiniteTimeout = TimeSpan.FromMilliseconds(-1); 
private const Int32 MAX_WAIT = 100; 

public static bool Wait(WaitHandle handle, TimeSpan timeout) 
{ 
    Int32 expireTicks; 
    bool signaled; 
    Int32 waitTime; 
    bool exitLoop; 

    // guard the inputs 
    if (handle == null) { 
     throw new ArgumentNullException("handle"); 
    } 
    else if ((handle.SafeWaitHandle.IsClosed)) { 
     throw new ArgumentException("closed wait handle", "handle"); 
    } 
    else if ((handle.SafeWaitHandle.IsInvalid)) { 
     throw new ArgumentException("invalid wait handle", "handle"); 
    } 
    else if ((timeout < InfiniteTimeout)) { 
     throw new ArgumentException("invalid timeout <-1", "timeout"); 
    } 

    // wait for the signal 
    expireTicks = (int)Environment.TickCount + timeout.TotalMilliseconds; 
    do { 
     if (timeout.Equals(InfiniteTimeout)) { 
      waitTime = MAX_WAIT; 
     } 
     else { 
      waitTime = (expireTicks - Environment.TickCount); 
      if (waitTime <= 0) { 
       exitLoop = true; 
       waitTime = 0; 
      } 
      else if (waitTime > MAX_WAIT) { 
       waitTime = MAX_WAIT; 
      } 
     } 

     if ((handle.SafeWaitHandle.IsClosed)) { 
      exitLoop = true; 
     } 
     else if (handle.WaitOne(waitTime, false)) { 
      exitLoop = true; 
      signaled = true; 
     } 
     else { 
      if (Application.MessageLoop) { 
       Application.DoEvents(); 
      } 
      else { 
       Thread.Sleep(1); 
      } 
     } 
    } 
    while (!exitLoop); 

    return signaled; 
}