2012-01-18 90 views
1

有人可以幫助我如何設置我的班級內的Thread.join()方法,或者如果有一個整潔的方式如何處理SynchronizationContext類和thread.join方法。基本上,即時通訊嘗試從不同的線程(而不是UI線程)每2秒更新一次datagridview(dgv)單元格和進度條(pb)。當一個線程完成這項工作時,該功能可以正常工作然而,我想設置2個線程,以便第一個線程(線程1)將更新控件(在我的情況下,它將更新datagridview並顯示10行,並且進度條將更新爲50%)。只要線程1完成了它的工作,線程2應該啓動並更新控件(在我的情況下,它將更新datagridview並顯示10多行並且進度條將更新爲100%)。請參閱下面的代碼。跨線程交互c#

using System; 
using System.Diagnostics; 
using System.Threading; 
using System.Windows.Forms; 

namespace DelegatesAndCallback 
{ 
public partial class Form1 : Form 
{ 
    private Thread newThread1; 
    private Thread newThread2; 

    public Form1() 
    { 
     InitializeComponent(); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 

     int id = Thread.CurrentThread.ManagedThreadId; 
     Trace.WriteLine("Button thread "+id); 

     SynchronizationContext uiContext = SynchronizationContext.Current; 

     // thread #1 
     startThread1(uiContext); 

     // thread #2 
     startThread2(uiContext); 
    } 

    public void startThread1(SynchronizationContext sc) 
    { 
     // thread #1 
     newThread1 = new Thread(Process1) { Name = "Thread 1" }; 
     newThread1.Start(sc); 
     //newThread1.Join(); 
    } 

    public void startThread2(SynchronizationContext sc) 
    { 
     // thread #2 
     newThread2 = new Thread(Process2) { Name = "Thread 2" }; 
     newThread2.Start(sc); 
     //newThread2.Join(); 
    } 

    public void updateProgressBarValue(object state) 
    { 
     double val = Convert.ToDouble(state)/19.0; 
     pb.Value = (int)(100*val); 
    } 

    public void updateDataGridViewValue(object state) 
    { 
     dgv.Rows.Add((int) state, (int) state); 
    } 

    public void Process1(object state) 
    { 
     SynchronizationContext uiContext = state as SynchronizationContext; 

     for (int i = 0; i < 10; i++) 
     { 
      uiContext.Send(updateDataGridViewValue, i); 
      uiContext.Send(updateProgressBarValue, i); 
      Thread.Sleep(2000); 
     } 
    } 

    public void Process2(object state) 
    { 
     SynchronizationContext uiContext = state as SynchronizationContext; 

     for (int i = 10; i < 20; i++) 
     { 
      if (uiContext != null) uiContext.Send(updateProgressBarValue, i); 
      if (uiContext != null) uiContext.Send(updateDataGridViewValue, i); 
      Thread.Sleep(2000); 
     } 
    } 
} 
} 
+0

究竟是你尋求幫助什麼叫UI更新?使用thread.join有什麼問題?爲什麼你需要兩個線程?你能從第一個開始第二個線程嗎? – Chris 2012-01-18 17:21:25

+0

@ Chris,你是對的Chris,如果我在第一個線程中添加第二個線程,它將起作用。然而,即時通訊嘗試使用thread.join方法,一旦第一個線程完成其作業,第二個線程就開始工作。 – 2012-01-18 17:29:54

+0

麻煩的是,thread.join我相信在等待其他線程完成時暫停當前線程的執行。因此,在你的例子中,如果你取消註釋了'.Join()',那麼你在同一個線程中做的沒有多大區別(有一些差異,但不是簡單的程序流程)。 – Chris 2012-01-19 18:48:50

回答

1

要同步線程,您應該使用[Manual | Auto] ResetEvents。 您應該使用其他模式編寫安全代碼。 調查我的代碼,請:

public interface IProgress 
{ 
    ManualResetEvent syncEvent { get; } 
    void updateProgressBarValue(int state); 
    void updateDataGridViewValue(int state); 
} 

public partial class Form1 : Form, IProgress 
{ 
    // Sync object will be used to syncronize threads 
    public ManualResetEvent syncEvent { get; private set; } 

    public Form1() 
    { 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     // Creeate sync object in unsignalled state 
     syncEvent = new ManualResetEvent(false); 

     // I like Async model to start background workers 
     // That code will utilize threads from the thread pool 
     ((Action<IProgress>)Process1).BeginInvoke(this, null, null); 
     ((Action<IProgress>)Process2).BeginInvoke(this, null, null); 
    } 

    public void updateProgressBarValue(int state) 
    { 
     // InvokeRequired? -> Invoke pattern will prevent UI update from the non UI thread 
     if (InvokeRequired) 
     { 
      // If current thread isn't UI method will invoke into UI thread itself 
      Invoke((Action<int>)updateProgressBarValue, state); 
      return; 
     } 

     double val = Convert.ToDouble(state)/19.0; 
     pb.Value = (int)(100 * val); 
    } 

    public void updateDataGridViewValue(int state) 
    { 
     if (InvokeRequired) 
     { 
      Invoke((Action<int>)updateDataGridViewValue, state); 
      return; 
     } 

     dgv.Rows.Add((int)state, (int)state); 
    } 

    public void Process1(IProgress progress) 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      // We have InvokeRequired in the methods and don't need any other code to invoke it in UI thread 
      progress.updateDataGridViewValue(i); 
      progress.updateProgressBarValue(i); 
      Thread.Sleep(2000); 
     } 

     // When thread 1 complete its job we will set sync object to signalled state to wake up thread 2 
     syncEvent.Set(); 
    } 

    public void Process2(IProgress progress) 
    { 
     // Thread 2 will stop until sync object signalled 
     syncEvent.WaitOne(); 

     for (int i = 10; i < 20; i++) 
     { 
      progress.updateProgressBarValue(i); 
      progress.updateDataGridViewValue(i); 
      Thread.Sleep(2000); 
     } 
    } 
} 

代碼進行了更新,從不同類別

+0

你的代碼有效,但我想知道如果我用'syncEvent.WaitOne()'添加一個新進程(進程3)似乎不起作用。你可以請啓蒙我嗎? – 2012-01-18 20:43:37

+0

如果您需要在線程2的同一時間啓動線程3,它將正常工作。但是,如果在線程2完成工作時需要啓動線程3,則應該使用另一個同步對象實例來實現它。 – 2012-01-19 14:54:44

+0

你的方法工作很多 – 2012-01-19 20:10:52

2

Control.Invoke(),這是專門設計用來讓非UI線程的事情像進度條進行交互。在這種情況下,使用Invoke會取代您的同步上下文和您使用其方法。

在一個稍微有關說明:一個更容易的方法來創建一個線程是:

new Thread(
() => { 
    /// insert code you want executed in a separate thread here... 
    } 
).Start(); 

更新 如果您需要更新來自不同類的進度條,我可能會做這樣的事情:

public partial class Form1 : Form 
{ 
    private ThreadOwner _threadOwner; 

    public Form1() 
    { 
     InitializeComponent(); 
     var _threadOwner = new ThreadOwner(); 
     _threadOwner.StartAThread(this,progressBar1.Minimum,progressBar1.Maximum); 
    } 

    protected override void OnClosing(CancelEventArgs e) 
    { 
     _threadOwner.Exit(); 

     base.OnClosing(e); 
    } 

    internal void SetProgress(int progress) 
    { 
     if (progressBar1.InvokeRequired) 
     { 
      progressBar1.Invoke(
       new Action<Form1>(
        (sender) => { 
         SetProgress(progress); 
        } 
        ),new[] { this } 
        ); 

     } 
     else 
      progressBar1.Value = progress; 
    } 
} 

而且ThreadOwner類:

public class ThreadOwner 
{ 
    private bool _done = false; 

    public void StartAThread(Form1 form, int min, int max) 
    { 
     var progress = min; 

     new Thread(() => 
      { 
       while (!_done) 
       { 
        form.SetProgress(progress); 

        if (progress++ > max) 
        { 
         progress = min; 
        } 
       } 

      } 
     ).Start(); 
    } 

    internal void Exit() 
    { 
     _done = true; 
    } 
} 

要點是線程需要對錶單實例的引用,這會暴露更新進度條的方法。然後該方法確保更新發生在正確的線程中。

+0

不是關於Thread.join的問題嗎?這當然是如何開始的,雖然我會承認,我對這個問題真的是半途而廢有點困惑...... – Chris 2012-01-18 17:22:15

+0

@David,我嘗試使用你建議的方法,我使用時唯一遇到的問題是我需要讓控件調用。如果我的線程函數在不同的類中,它不會工作 – 2012-01-18 17:22:42

+0

@ user945511在您的示例中,所有內容都在同一個類中。如果您需要在單獨的類中更新進度欄,請在您的表單中公開一個線程調用的方法,然後執行Invoke()。在任何情況下,其他類都不應直接引用表單上的控件。 – 2012-01-18 17:27:59