2012-01-18 88 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