C # не-интерфейсный многопоточный вызов - PullRequest
2 голосов
/ 18 ноября 2010

Я пытаюсь сделать перекрестные вызовы в C #.

Всякий раз, когда я вызываю методы объекта, созданного в контексте потока A, из статического метода, вызываемого из потока B, метод всегда выполняетсяв потоке B. Я не хочу этого, я хочу, чтобы он работал в том же потоке, что и поток. Объект, методы которого я вызываю.

Invoke прекрасно работает для вызовов пользовательского интерфейса, и я прочитал десятки статейи SO ответы, относящиеся к различным способам выполнения перекрестных вызовов Forms / WPF.Однако что бы я ни пытался (обработка событий, делегаты и т. Д.), Метод объекта потока А всегда будет запускаться в потоке Б, если он вызывается потоком Б.

В какую часть библиотеки мне следует обратиться, чтобы решить эту проблему?Если это актуально, поток B в настоящий момент «вращается», читает из сетевого порта и иногда вызывает метод объекта потока A через делегата, который был создан в потоке A и передан с использованием ParameterizedThreadStart.

Я не ищучтобы изменить парадигму, просто отправьте сообщение (запрос на вызов метода) из одного потока (поток B) в другой (поток A).

РЕДАКТИРОВАТЬ:

Мой вопрос был «какая частьиз библиотеки я должен искать, чтобы решить это?Ответ, кажется, нет.Если я хочу четко разграничить потребление и опрос, мне придется написать собственный код, чтобы справиться с этим.

Ответы [ 4 ]

6 голосов
/ 18 ноября 2010

Всякий раз, когда я вызываю методы объекта, работающего в потоке A

Объекты не работают в потоках.

Для того, чтобы это работало, вам нужно создать какую-то очередь, в которую вы можете поместить делегата, которая будет регулярно проверяться в основном цикле потока А. Примерно так, предполагая, что Something.MainThreadLoop является точкой входа для потока A:

public class Something
{
    private Queue<Action> actionQueue = new Queue<Action>();

    private volatile bool threadRunning = true;

    public void RunOnThread(Action action)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        lock (actionQueue)
            actionQueue.Enqueue(action);
    }

    public void Stop()
    {
        threadRunning = false;
    }

    private void RunPendingActions()
    {
        while (actionQueue.Count > 0) {
            Action action;

            lock (actionQueue)
                action = actionQueue.Dequeue();

            action();
        }
    }

    public void MainThreadLoop()
    {
        while (threadRunning) {
            // Do the stuff you were already doing on this thread.

            // Then, periodically...
            RunPendingActions();
        }
    }
}

Затем, учитывая ссылку на объект Something, вы можете сделать это:

something.RunOnThread(() => Console.WriteLine("I was printed from thread A!"));
3 голосов
/ 18 ноября 2010

Код работает на потоках.Объекты не привязаны (как правило, см. Локальный поток) к определенному потоку.Выполняя WinFormControl.Invoke или WPFControl.Invoke, вы публикуете сообщение в Message Pump или Dispatcher соответственно для запуска некоторого кода позднее.

Насос сообщений выглядит примерно так:

Message message;
while(GetMessage(&message))
{
    ProcessMessage(message);
}

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

Редактировать:

Что это, я думаю, вынужно использовать шаблон Producer Consumer.

http://msdn.microsoft.com/en-us/library/yy12yx1f(VS.80).aspx

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

Поток A делает «гораздо более важные вещи».Тема B крутится, слушает сообщения.Поток C потребляет эти сообщения.

Нет необходимости в сортировке по потокам.

1 голос
/ 18 ноября 2010

РЕДАКТИРОВАТЬ: Я думаю, что вы, вероятно, хотите использовать класс System.Threading.AutoResetEvent.В документации MSDN есть достойный пример того, как один поток ожидает другого, и я думаю, что он похож на то, что вы пытаетесь сделать: http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx В частности, обратите внимание на вызовы trigger.WaitOne () и trigger.Set ()

EDIT2: добавлена ​​опция № 3 после чтения нового комментария из OP.

«Всякий раз, когда я вызываю методы объекта, работающего в потоке A ...» - объект не 't «запускается» в потоке и на самом деле не принадлежит ни одному потоку, независимо от того, в каком потоке был создан объект.

Учитывая, что ваш вопрос касается «межпотокового вызова без пользовательского интерфейса», я полагаюуже знакомы с "вызовом перекрестного потока пользовательского интерфейса".Я вижу, как WinForms может создать у вас впечатление, что поток владеет объектом, и вам необходимо «отправить сообщение» потоку, чтобы он что-то сделал.

Управляющие объекты WinForm являются своего родаособый случай в том, что они просто не работают должным образом, если вы взаимодействуете с ними с потоком, который не является тем, который их создал, но это не вызвано тем, как взаимодействуют потоки и объекты.

В любом случае,Перейдем к решению вашего вопроса.

Во-первых, вопрос, который прояснит проблему: вы упомянули, что делает Поток B, но что делает Поток A до того, как Поток B вызывается?

Вот несколько идей, которые, я думаю, соответствуют тому, что вы хотите сделать:

  1. Не создавайте поток А, пока вам это не нужно.Вместо того, чтобы поток B «отправлял сообщение в поток A», лучше попросите поток B создать поток A (или назовите его потоком C, если хотите) и заставьте его начать выполняться в это время.

  2. Если вам нужен поток A, который уже существует, и вы хотите, чтобы поток A обрабатывал только события потока B по одному, вы можете подождать, пока поток A не получит уведомление от потока BВзгляните на класс System.Threading.WaitHandle (интересующие производные классы - ManualResetEvent и AutoResetEvent).

    Поток A в какой-то момент вызовет WaitHandle.WaitOne (), который будетзаставьте его остановиться и подождать, пока поток B не вызовет WaitHandle.Set () для того же объекта WaitHandle.

  3. Если поток A занят другими делами, возможно, вы захотите настроить некоторые из них.вид переменной флага.Подобно концепции WaitHandle в # 2, но вместо того, чтобы заставлять поток A делать паузу, вы просто хотите, чтобы поток B установил флаг (возможно, просто логическую переменную), который будет сигнализировать потоку A, что ему нужно что-то делать.Пока поток А занят другими делами, он может периодически проверять этот флаг, чтобы решить, есть ли работа, которую нужно выполнить.

Выполняет ли метод, на котором будет выполняться поток А,Ваш объект требует какого-либо ввода из темы B?Затем, прежде чем поток B вызовет WaitHandle.Set (), пусть он поместит некоторые данные в очередь или что-то в этом роде.Затем, когда поток A «активирован», он может извлечь эти данные из очереди и приступить к выполнению метода объекта с использованием этих данных.Используйте механизм блокировки (например, оператор блокировки C #) для синхронизации доступа к очереди.

1 голос
/ 18 ноября 2010

То, что вам нужно будет сделать, - это запустить своего рода Очередь и заставить Поток А наблюдать за этой очередью для работы. Когда поток A видит новую работу, попавшую в очередь, он может снять ее с очереди и выполнить работу, а затем вернуться к ожиданию большего.

Вот некоторый псевдокод:

    public class ThreadAQueue
    {
        private Queue<delegate> _queue;
        private bool _quitWorking;

        public void EnqueueSomeWork(delegate work)
        {
            lock(_queue) 
            {
                _queue.Enqueue(work);
            }
        }

        private void DoTheWork()
        {
            while(!quitWorking) 
            {
                delegate myWork;

                lock(_queue)
                {
                    if(_queue.Count > 1)
                        myWork = _queue.Dequeue();
                }

                myWork();
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...