在另一个线程中运行事件处理程序(无需阻塞线程)

4

我有一个名为Communicator的类,它在后台线程上接收TCP端口上的数据。

Communicator有一个事件OnDataReceived,其类型为EventHandler<DataReceivedEventArgs>

还有另一个名为Consumer的类,其中包含一个订阅了Communicator.OnDataReceived事件的方法。

comm.OnDataReceived += consumer.PresentData;
Consumer类是在表单构造函数中创建的,然后在另一个线程上调用其方法。该方法是一个无限循环,因此在应用程序执行期间会一直停留在该方法中。
我想要做的是,使Communicator.OnDataReceived事件在消费者线程上调用consumer.PresentData方法。
这是否有可能?如果可能,我应该使用什么样的机制(同步类)?

1
抱歉,但并不是很清楚,当你说消费者在其他线程上运行时。它是否具有无限线程运行的功能或者对象是在其他线程中创建的,还是其他什么情况? - Tigran
@Tigran 我已经更新了问题以回答你的评论。 - Kornelije Petak
这里的“消费者线程”是哪一个线程?GUI 线程还是另一个线程? - H H
@Henk 有三个线程。一个是GUI(启动)线程,另一个是TCP线程,第三个是消费者的线程。 - Kornelije Petak
好的,你想在哪一个上面触发事件? - H H
通信器引发事件,我希望在消费者(第三个)线程上通过事件处理程序来消耗它们。 - Kornelije Petak
4个回答

4
将以下代码添加到您的代码中:(我通常将其放在名为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());

这会在 'consumer' 上调用 'consumer.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());
    }
}

基本上,您正在创建需要进行调用的所有内容,并将其直接传递给通信器。这解决了在通信器中具有消费者实例或引用的必要性。

还要注意,我没有测试任何内容,我只是在理论上做这些,但应该可以很好地工作。


问题是,通信器在一个单独的程序集中,并且没有引用消费者类。情况恰恰相反。如果我理解你的答案,我的客户端类将不得不实现ISynchronizeInvoke。我以前从未使用过它。更不用说尝试去实现它了。我需要进一步研究一下。 - Kornelije Petak
@danderson 是的,我知道。我无法合并这些程序集,所以唯一能做的就是实现ISynchronizeInvoke,并将消费者的实例作为ISynchronizeInvoke对象传递。但我想知道是否存在设计缺陷。 - Kornelije Petak

1

1

这是可能的。您可以创建一个执行队列,或查看 Dispatcher 对象,它很有用(有时是唯一的方法)将某些方法推入 UI 线程。


1

只有目标线程设计为接受传输方法执行的调度操作时,您才能在线程上获取执行方法的方法。

使其工作的一种方法是让您的Consumer类实现ISynchronizeInvoke。然后让您的Communicator类接受一个ISynchronizeInvoke实例,它可以用来执行调度操作。以System.Timers.Timer类为例。 System.Timers.Timer具有SynchronizingObject属性,它可以使用该属性通过调用ISynchronizeInvoke.InvokeISynchronizeInvoke.BeginInvokeElapsed事件调度到托管同步对象的线程上。

棘手的部分是如何在Consumer类上实现ISynchronizeInvoke。由该类启动的工作线程必须实现生产者-消费者模式以能够处理委托。 BlockingCollection类会使这相对容易,但仍然需要学习曲线。尝试一下,如果需要更多帮助,请回复并提出更具体的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接