В C # каков рекомендуемый способ передачи данных между двумя потоками? - PullRequest
25 голосов
/ 02 декабря 2008

У меня есть основной поток с графическим интерфейсом и второй поток, работающий внутри собственного ApplicationContext (чтобы сохранить его работоспособным, даже если нет никакой работы). Я хочу вызвать метод на моем втором потоке из моего потока GUI, но если я просто вызову thread.Method (); Кажется, он работает в моем основном потоке графического интерфейса и приводит к тому, что мой интерфейс перестает отвечать на запросы. Каков наилучший способ вызова методов в разных потоках?

Обновление: Что я действительно хочу сделать, так это общаться между двумя потоками, а не общаться с графическим интерфейсом. GUI как раз является одним из потоков, которые должны будут взаимодействовать с моим вторым потоком.

Обновление № 2: Хорошо, я действительно что-то упускаю. Я создал событие и делегата, и мой рабочий поток подписался на событие. Но когда я звоню Invoke (MyEvent); из моего потока GUI работа, которую выполняет рабочий поток, заканчивается тем, что он находится в потоке GUI, и останавливает поток GUI, пока не завершит обработку. Возможно ли то, что я пытаюсь сделать, даже без опроса статического объекта?

Ответы [ 9 ]

39 голосов
/ 03 декабря 2008

Ничего себе, я не могу поверить, как люди не удосужились прочитать вопрос.

В любом случае, это то, что я делаю.

  1. Создайте свои "сообщения" классов. Здесь хранится вся информация, которой вы хотите поделиться.
  2. Создать очередь для каждого потока. Используйте SyncLock (C # lock) для чтения / записи в него.
  3. Если вы хотите поговорить с потоком, отправьте ему объект сообщения с копией всей необходимой информации, добавив сообщение в очередь.
  4. Затем рабочий поток может читать из очереди, читая и обрабатывая каждое сообщение по порядку. Когда сообщений нет, просто спите.

Убедитесь, что вы не разделяете объекты между двумя потоками. Как только ваш поток GUI вставляет сообщение в очередь, поток GUI больше не владеет сообщением. Он не может содержать ссылку на сообщение, иначе вы попадете в беду.

Это не даст вам наилучшей производительности, но будет достаточным для большинства приложений. И что еще более важно, будет намного сложнее ошибиться.

ОБНОВЛЕНИЕ: не используйте SyncLock и Queue. Вместо этого используйте ConcurrentQueue, который будет автоматически обрабатывать любые блокировки. Вы получите лучшую производительность и с меньшей вероятностью ошибетесь.

12 голосов
/ 02 декабря 2008

.Net уже поставляется с классом <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx" rel="noreferrer">System.ComponentModel.BackgroundWorker</a> специально для обработки фоновых задач и связи с графическим интерфейсом. Используйте это.

6 голосов
/ 25 апреля 2011

Чувак, прочитай бесплатную электронную книгу Албахари. Я подключен к нему любым способом, так что это не вилка. Я читал это, мои коллеги читали, и я использовал это много раз.

Я бы порекомендовал создать класс производителя / потребителя, в котором вы можете запустить один ожидающий поток (не блокирующий), поставить задачи в очередь и дать ему сигнал для начала работы.

просто Google для этого.

6 голосов
/ 02 декабря 2008

По сути, вы создали версию ThreadPool для бедного человека. Ваш второй поток просто сидит, ничего не делая, и без достаточного количества работы с вашей стороны, вы не можете просто заставить его делать работу за вас. Вы должны были бы передать делегатов в очередь, которую ваш поток затем снимает и выполняет.

Лучше всего делать то, что вы хотели, и просто использовать .NET ThreadPool и дать ему работу.

3 голосов
/ 02 декабря 2008

Я предполагаю, что какое-то событие в графическом интерфейсе требует запуска длительной задачи, которая запускается в фоновом режиме - для этого есть два основных способа. Если вы просто хотите вызвать метод в другом потоке, вы можете сделать это с помощью Асинхронный вызов синхронных методов . Я обычно делаю что-то вроде этого:



//delegate with same prototype as the method to call asynchrously
delegate void ProcessItemDelegate(object item);

//method to call asynchronously
private void ProcessItem(object item) { ... }

//method in the GUI thread
private void DoWork(object itemToProcess)
{
    //create delegate to call asynchronously...
    ProcessItemDelegate d = new ProcessItemDelegate(this.ProcessItem);
    IAsyncResult result = d.BeginInvoke(itemToProcess,
                                        new AsyncCallback(this.CallBackMethod),
                                        d); 
}

//method called when the async operation has completed
private void CallbackMethod(IAsyncResult ar)
{
    ProcessItemDelegate d = (ProcessItemDelegate)ar.AsyncState;
    //EndInvoke must be called on any delegate called asynchronously!
    d.EndInvoke(ar);
}

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

В качестве альтернативы вы можете использовать общее состояние для связи между потоками и использовать EventWaitHandle , чтобы сигнализировать об обновлениях общего состояния - в этом примере метод в графическом интерфейсе добавляет рабочие элементы в очередь для обработки в фон. Рабочий поток обрабатывает элементы из очереди, когда работа становится доступной.


//shared state
private Queue workQueue;
private EventWaitHandle eventHandle;

//method running in gui thread
private void DoWork(Item itemToProcess)
{
   //use a private lock object instead of lock...
   lock(this.workQueue)
   {
       this.workQueue.Add(itemToProcess);
       this.eventHandle.Set();
   }
}

//method that runs on the background thread
private void QueueMonitor()
{
   while(keepRunning)
   {
       //if the event handle is not signalled the processing thread will sleep here until it is signalled or the timeout expires
       if(this.eventHandle.WaitOne(optionalTimeout))
       {
           lock(this.workQueue)
           {
              while(this.workQueue.Count > 0)
              {
                 Item itemToProcess = this.workQueue.Dequeue();
                 //do something with item...
              }
           }
           //reset wait handle - note that AutoResetEvent resets automatically
           this.eventHandle.Reset();
       }
   }
}
2 голосов
/ 03 декабря 2008

Удобство Control.BeginInvoke () трудно упустить. Вам не нужно. Добавьте новый класс в ваш проект и вставьте этот код:

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

public partial class frmWorker : Form {
  public frmWorker() {
    // Start the worker thread
    Thread t = new Thread(new ParameterizedThreadStart(WorkerThread));
    t.IsBackground = true;
    t.Start(this);
  }
  public void Stop() {
    // Synchronous thread stop
    this.Invoke(new MethodInvoker(stopWorker), null);
  }
  private void stopWorker() {
    this.Close();
  }
  private static void WorkerThread(object frm) {
    // Start the message loop
    frmWorker f = frm as frmWorker;
    f.CreateHandle();
    Application.Run(f);
  }
  protected override void SetVisibleCore(bool value) {
    // Shouldn't become visible
    value = false;
    base.SetVisibleCore(value);
  }
}

Вот пример кода для проверки:

  public partial class Form1 : Form {
    private frmWorker mWorker;
    public Form1() {
      InitializeComponent();
      mWorker = new frmWorker();
    }

    private void button1_Click(object sender, EventArgs e) {
      Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
      mWorker.BeginInvoke(new MethodInvoker(RunThisOnThread));
    }
    private void RunThisOnThread() {
      Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
    }

    private void button2_Click(object sender, EventArgs e) {
      mWorker.Stop();
    }
  }
1 голос
/ 12 января 2009

Вы можете использовать события или, как сказал Grauenwolf, - сигнал сообщения. Я обертываю каждый свой поток как управляющий синглтон, оттуда вы можете легко реализовать любой. Вы могли бы даже сделать публичные свойства бедняков, чтобы перевернуть биты.

Также вы можете реализовать конечный автомат, и вместо передачи сообщений каждый поток может контролировать друг друга

1 голос
/ 02 декабря 2008

Поместите цикл во второй поток, который большую часть времени спит, но каждый [Интервал] он просыпается и проверяет общую переменную, которая сообщает ему, следует ли запускать ваш метод или нет, и если этот общий логический параметр установлен на Значение true, затем он запускает метод, который выполняет любую задачу, которую вы пытаетесь выполнить ... В этом методе метод должен собрать данные, необходимые из другой общей переменной.

В основном потоке графического интерфейса поместите данные в общую переменную параметра метода, а затем установите для логической переменной общего доступа «Выполнить» значение true ...

Внутри рабочего метода не забудьте сбросить переменную общего запуска "bool" в false, когда вы закончите, поэтому цикл не будет выполняться; один и тот же экземпляр будет выполняться снова и снова ...

1 голос
/ 02 декабря 2008

Используйте объект синхронизации, чтобы сообщить потоку, что ему нужно обработать новые данные (или новое состояние графического интерфейса). Один из относительно простых способов сделать это - использовать объект события. Вот краткий обзор того, как это будет работать:

  1. Поток GUI 2-го потока совместно использует объект события (поэтому они оба знают об этом)
  2. 2-й поток обычно выполняется в каком-то цикле, и каждый раз, когда он ожидает сигнала о событии
  3. Поток GUI сигнализирует о событии, когда ему требуется, чтобы 2-й поток что-то сделал
  4. Когда 2-й поток завершен, он сбрасывает событие и снова ждет (или выходит)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...