Запускать обработчики событий в другом потоке (без блокировки потоков) - PullRequest
4 голосов
/ 07 июля 2011

У меня есть класс Communicator, который работает в фоновом потоке, получающем данные через порт TCP.

Коммуникатор имеет событие OnDataReceived типа EventHandler<DataReceivedEventArgs>.

Существует еще один класс Consumer, который содержит метод, подписанный на событие Communicator.OnDataReceived.

comm.OnDataReceived += consumer.PresentData;

Класс Consumer создается в конструкторе Form, а затем один из его методов вызывается в другом потоке.Этот метод представляет собой бесконечный цикл, поэтому он остается в этом методе во время выполнения приложения.

Я хотел бы, чтобы событие Communicator.OnDataReceived вызывало метод consumer.PresentData в потоке потребителя.

Это даже почти возможно?И если да, то какие механизмы (классы синхронизации) мне следует использовать?

Ответы [ 4 ]

4 голосов
/ 07 июля 2011

Добавьте это где-нибудь в вашем коде: (Я обычно помещаю это в статический вспомогательный класс ISynchronizedInvoke, чтобы я мог вызвать ISynchronizedInvoke.Invoke (...));

public static void Invoke(ISynchronizeInvoke sync, Action action) {
    if (!sync.InvokeRequired) {
        action();
    }
    else {
        object[] args = new object[] { };
        sync.Invoke(action, args);
    }
}

Тогда внутри OnDataReceived вы можете сделать:

Invoke(consumer, () => consumer.PresentData());

Это вызывает «customer.PresentData» для «потребителя».

Что касается вашей проблемы проектирования (коммуникатор с отзывами потребителей), вы можете ввести метод внутри коммуникатора, например:

class Communicator {
    private ISynchronizeInvoke sync;
    private Action syncAction;

    public void SetSync(ISynchronizeInvoke sync, Action action) {
        this.sync = sync;
        this.syncAction = action;
    }

    protected virtual void OnDataReceived(...) {
        if (!sync.InvokeRequired) {
            syncAction();
        }
        else {
            object[] args = new object[] { };
            sync.Invoke(action, args);
        }
    }
}

Это даст вам способ передать ISynchronizedInvoke из вашего потребительского класса. Таким образом, вы будете создавать ISynchronizedInvoke в потребительской сборке.

class Consumer {
    public void Foo() {
        communicator.SetSync(this, () => this.PresentData());
    }
}

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

Также обратите внимание, что я не проверял ничего из этого, я делаю все это в теории, но это должно хорошо работать.

1 голос
/ 07 июля 2011

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

Один способзаставить это работать, чтобы ваш Consumer класс реализовывал ISynchronizeInvoke.Затем пусть ваш класс Communicator принимает экземпляр ISynchronizeInvoke, который он может использовать для выполнения операции маршалинга.Взгляните на класс System.Timers.Timer в качестве примера.System.Timers.Timer имеет свойство SynchronizingObject, которое можно использовать для перенаправления события Elapsed в поток, содержащий синхронизирующий объект, путем вызова ISynchronizeInvoke.Invoke или ISynchronizeInvoke.BeginInvoke.

Сложная часть заключается в том, как реализоватьISynchronizeInvoke по классу Consumer.Рабочий поток, запущенный этим классом, должен будет реализовать шаблон производитель-потребитель, чтобы иметь возможность обрабатывать делегаты.Класс BlockingCollection сделает это относительно легко, но все еще есть довольно сложная кривая обучения.Сделайте попытку и отправьте ответ с более сфокусированным вопросом, если вам нужна дополнительная помощь.

1 голос
/ 07 июля 2011

Это должно быть возможно.Вы можете создать очередь для выполнения или посмотреть на объект Dispatcher, полезно (и иногда обязательно как единственный способ) вставить некоторые методы в поток пользовательского интерфейса, это помогает.

1 голос
/ 07 июля 2011

Попробуйте использовать BackgroundWorker класс.

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