2008-11-19 80 views
2

我的表單上有很長的用戶界面操作,每當觸發事件時都會觸發該操作。在操作發生時,我不想讓UI塊阻塞,我想在另一個線程中執行操作,並在該事件再次觸發時中止該線程並重新開始。我可以創建一個可以修改用戶界面的線程嗎?我可以放棄嗎?

但是,爲了安全地更改窗體上的控件,我需要使用窗體的Invoke或BeginInvoke方法。如果我這樣做,那麼我可以把我所有的UI操作的一個功能是這樣的:

private delegate void DoUIStuffDelegate(Thing1 arg1, Thing2 arg2); 
private void doUIStuff(Thing1 arg1, Thing2 arg2) 
{ 
    control1.Visible = false; 
    this.Controls.Add(arg1.ToButton()); 
    ... 
    control100.Text = arg2.ToString(); 
} 

... 

private void backgroundThread() 
{ 
    Thing1 arg1 = new Thing1(); 
    Thing2 arg2 = new Thing2(); 

    this.Invoke(new DoUIStuffDelegate(doUIStuff), arg1, arg2); 
} 

Thread uiStuffThread = null; 

public void OnEventFired() 
{ 
    if (uiStuffThread != null) 
     uiStuffThread.Abort(); 

    uiStuffThread = new Thread(backgroundThread); 
    uiStuffThread.Start(); 
} 

,但如果我這樣做,然後我失去了一個單獨的線程工作的好處。或者,我可以把他們各自在自己的功能是這樣的:

private delegate void DoUIStuffLine1Delegate(); 
private delegate void DoUIStuffLine2Delegate(Thing1 arg1); 
... 

private delegate void DoUIStuffLine100Delegate(Thing2 arg2); 

private void doUIStuffLine1() 
{ 
    control1.Visible = false; 
} 

private void doUIStuffLine2() 
{ 
    this.Controls.Add(arg1.ToButton()); 
} 

... 

private void doUIStuffLine100(Thing2 arg2) 
{ 
    control100.Text = arg2.ToString(); 
} 

... 

private void backgroundThread() 
{ 
    Thing1 arg1 = new Thing1(); 
    Thing2 arg2 = new Thing2(); 

    this.Invoke(new DoUIStuffLine1Delegate(doUIStuffLine1)); 
    this.Invoke(new DoUIStuffLine2Delegate(doUIStuffLine2), arg1); 
    ... 
    this.Invoke(new DoUIStuffLine100Delegate(doUIStuffLine100), arg2); 
} 

Thread uiStuffThread = null; 

public void OnEventFired() 
{ 
    if (uiStuffThread != null) 
     uiStuffThread.Abort(); 

    uiStuffThread = new Thread(backgroundThread); 
    uiStuffThread.Start(); 
} 

但是這是一個可怕的,難以維護的混亂。有沒有辦法創建一個可以修改用戶界面的線程,並且我可以放棄?所以,我可以做這樣的事情:

private void doUIStuff() 
{ 
    Thing1 arg1 = new Thing1(); 
    Thing2 arg2 = new Thing2(); 

    control1.Visible = false; 
    this.Controls.Add(arg1.ToButton()); 
    ... 
    control100.Text = arg2.ToString(); 
} 

Thread uiStuffThread = null; 

public void OnEventFired() 
{ 
    if (uiStuffThread != null) 
     uiStuffThread.Abort(); 

    uiStuffThread = this.GetNiceThread(doUIStuff); 
    uiStuffThread.Start(); 
} 

,而不必我的表格上禁用跨線程檢查?理想情況下,我希望能夠在線程或方法中設置一些屬性,這些屬性單獨包裝了代表中的所有操作,然後在表單的線程中調用該委託。

回答

3

首先 - 不要禁用跨線程檢查...形式有螺紋的親和力......

二 - 儘量避免中止線程;它是不是很好 - 你應該更喜歡乾淨關閉(如BackgroundWorker的支持取消)

一個選擇可能是編寫一個包裝方法:

  • 接受一個類型化的委託(這樣你就可以調用它更簡單)
  • 不必要的檢查(拋出異常終止並展開)

例如:

void worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     try { 
      Action<Action> update = thingToDo => 
      { 
       if (worker.CancellationPending) throw new SomeException(); 
       this.Invoke(thingToDo); 
      }; 

      //... 
      string tmp = "abc"; // long running 
      update(() => this.Text = tmp); 

      tmp = "def"; // long running 
      update(() => textbox1.Text = tmp); 
     } catch (SomeException) { 
      e.Cancel = true; 
     } 
    } 

這還是有點凌亂,但可以說比中止線程清潔左,右......

0

您可以使用在框架2.介紹好吧,這是同樣的事情對象的SynchronizationContext避免使用調用的,你可以用一個替代另一個,但事實上,它更高效,更強大。 無論如何,跨線程需要檢查,因爲如果沒有這個線程,你永遠無法訪問在另一個線程中創建的控件。

閱讀關於這一點: http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspx http://codingly.com/2008/08/04/invokerequired-invoke-synchronizationcontext/

一些代碼暴露我的想法:

private Thread workerThread; 

     private AsyncOperation operation; 

     public event EventHandler SomethingHappened; 

     public MySynchronizedClass() 
     { 
      operation = AsyncOperationManager.CreateOperation(null); 

      workerThread = new Thread(new ThreadStart(DoWork)); 

      workerThread.Start(); 
     } 

     private void DoWork() 
     { 
      operation.Post(new SendOrPostCallback(delegate(object state) 
      { 
       EventHandler handler = SomethingHappened; 

       if(handler != null) 
       { 
        handler(this, EventArgs.Empty); 
       } 
      }), null); 

      operation.OperationCompleted(); 
     } 
+0

所以要取消線程,我可以再次調用operation.Post()與拋出異常的委託函數?或者會等到第一次操作.Post()完成了嗎? – Simon 2008-11-20 08:58:31

+0

...或將PostOperationCompleted中止操作? – Simon 2008-11-20 09:19:00

+0

它不會中止,它只會完成異步操作。我認爲你甚至可以忽略它。另一種方法是不使用AsyncOperationManager,但它被認爲更好: SynchronizationContext syncContext; this.syncContext = SynchronizationContext.Current; this.syncContext.Post(delegate {},null) – netadictos 2008-11-20 10:35:05

0

我回答你在這裏,因爲評論是太短了。 我個人使用BackgroundWorker,當方法結束時,線程被釋放。基本上你要做的就是:

bw = new BackgroundWorkerExtended(); 
bw.DoWork += (DoWorkEventHandler)work; 
bw.WorkerSupportsCancellation = true; 
//bw.WorkerReportsProgress = true; 
bw.RunWorkerCompleted += (RunWorkerCompletedEventHandler)workCompleted; 
//bw.ProgressChanged+=new ProgressChangedEventHandler(bw_ProgressChanged); 
bw.RunWorkerAsync(); 

,如果你使用後的另一件事情,這將是異步的,如果你使用,發送這將是同步的。

取消: bw.CancelAsync();

0

除了BackgroundWorker的建議和相關的評論,BackgroundWorker會將OnProgress回調封送回UI線程,因此您可以從那裏更新內容而無需執行Invoke()。

我真的不知道這是否有幫助,無論如何,這是一種技術,你可以很容易地使用,沒有後臺工作人員。

我懷疑如果後臺任務需要了解bazillion調用的表單,那麼您可能會有一個值得關注的分離問題。

相關問題