Question

can someone help me how to set Thread.join() method within my class or if there is a neat way how to deal with SynchronizationContext class and thread.join method. basically, im trying to update a datagridview (dgv) cell and progress bar (pb) every 2 seconds from a different thread (not UI thread). the functionality works fine when one thread does the job; however, i would like to set 2 threads so that the first thread (Thread 1) will update the controls (in my case, it will update the datagridview and display 10 rows and the progress bar will be update to 50%). as soon as thread 1 has done its job, Thread 2 should start and update the controls (in my case, it will update the datagridview and display 10 more rows and the progress bar will be update to 100%). Please see code below.

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);
        }
    }
}
}
Was it helpful?

Solution

To syncronize threads you should use [Manual|Auto]ResetEvents. You should use other patterns to write a safe code. Investigate my code please:

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);
        }
    }
}

Code was updated to call UI update from the different classes

OTHER TIPS

See Control.Invoke(), which is specifically designed to let non-UI threads interact with things like progress bars. In this case, use of Invoke would replace your synchronization context and your use of its Send() method.

On a slightly related note: a far easier way to create a thread is:

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

Update If you need to update the progress bar from a different class, I might do something like this:

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;
    }
}

And the ThreadOwner class:

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;
    }
}

The gist is that the thread needs a reference to your form instance, which exposes a method to update the progress bar. That method then makes sure that the update happens in the correct thread.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top