Search code examples
c#multithreadingsynchronizationcontext

cross-thread interaction c#


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

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