межпотоковое взаимодействие С#

может кто-нибудь помочь мне, как установить метод Thread.join() в моем классе, или если есть изящный способ, как работать с классом SynchronizationContext и методом thread.join. в основном, я пытаюсь обновить ячейку datagridview (dgv) и индикатор выполнения (pb) каждые 2 секунды из другого потока (не потока пользовательского интерфейса). функциональность работает нормально, когда работу выполняет один поток; однако я хотел бы установить 2 потока, чтобы первый поток (поток 1) обновлял элементы управления (в моем случае он обновлял представление данных и отображал 10 строк, а индикатор выполнения обновлялся до 50%). как только поток 1 выполнит свою работу, поток 2 должен запуститься и обновить элементы управления (в моем случае он обновит datagridview и отобразит еще 10 строк, а индикатор выполнения будет обновлен до 100%). Пожалуйста, смотрите код ниже.

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

person Carlitos Overflow    schedule 18.01.2012    source источник
comment
В чем именно вы просите помощи? Какие проблемы у вас возникают при использовании thread.join? Зачем вам две нити? Можете ли вы просто начать второй поток с первого?   -  person Chris    schedule 18.01.2012
comment
@Chris, ты прав, Крис, если я добавлю второй поток в первый, он будет работать. Однако я пытаюсь использовать метод thread.join, чтобы, как только первый поток завершил свою работу, второй поток начал свою работу.   -  person Carlitos Overflow    schedule 18.01.2012
comment
Проблема в том, что thread.join, я считаю, останавливает выполнение текущего потока, ожидая завершения другого потока. Таким образом, в вашем примере, если вы раскомментировали .Join(), вы не сильно отличались бы от того, что просто делаете это в том же потоке (есть некоторые различия, но не с точки зрения простого потока программы).   -  person Chris    schedule 19.01.2012


Ответы (2)


Для синхронизации потоков вы должны использовать [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);
        }
    }
}

Код был обновлен для вызова обновления пользовательского интерфейса из разных классов.

person Viacheslav Smityukh    schedule 18.01.2012
comment
Ваш код работает, но я просто хотел бы знать, добавляю ли я новый процесс (процесс 3) с syncEvent.WaitOne(), похоже, он не работает. не могли бы вы просветить меня? - person Carlitos Overflow; 19.01.2012
comment
Если вам нужно запустить поток 3 одновременно с потоком 2, он будет работать правильно. Но если вам нужно запустить поток 3, когда поток 2 закончит свою работу, вы должны использовать другой экземпляр объекта синхронизации для его реализации. - person Viacheslav Smityukh; 19.01.2012

См. Control.Invoke(), специально предназначенный для Потоки пользовательского интерфейса взаимодействуют с такими вещами, как индикаторы выполнения. В этом случае использование Invoke заменит ваш контекст синхронизации и использование вами его метода Send().

В немного связанной заметке: гораздо более простой способ создать поток:

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

Суть в том, что потоку нужна ссылка на ваш экземпляр формы, который предоставляет метод для обновления индикатора выполнения. Затем этот метод гарантирует, что обновление происходит в правильном потоке.

person 3Dave    schedule 18.01.2012
comment
Разве вопрос не в Thread.join? Это, безусловно, то, как это начинается, хотя я признаю, что я немного запутался в том, что вопрос действительно был на полпути... - person Chris; 18.01.2012
comment
@ Дэвид, я пытаюсь использовать предложенный вами метод, единственная проблема, с которой я столкнулся при использовании, заключается в том, что мне нужно иметь контроль для вызова. что в случае, если мои функции потока находятся в другом классе, это не сработает - person Carlitos Overflow; 18.01.2012
comment
@ user945511 В вашем примере все находится в одном классе. Если вам нужно обновить индикатор выполнения в отдельном классе, предоставьте метод в своей форме, который вызывает поток, который затем выполняет Invoke(). Ни в коем случае другие классы не должны напрямую ссылаться на элементы управления в форме. - person 3Dave; 18.01.2012
comment
@ Крис, да, но его подход кажется излишним для описанной проблемы. - person 3Dave; 19.01.2012
comment
@DavidLively: Да, может быть. Но я думаю, что на вопрос всегда следует отвечать вместе с комментарием, объясняющим, почему это неправильный ответ. Таким образом, он может точно узнать, почему thread.join не подходит (к сожалению, хотя я думаю, что понимаю это, я не делал много многопоточных вещей, поэтому я недостаточно уверен, чтобы дать ответ самостоятельно). - person Chris; 19.01.2012