如何在.NET中的非UI线程中实现消息泵?

时间:2010-04-10 15:50:59

标签: c# multithreading message

我正在阅读此博客:Use Threads Correctly,我想知道:

如何在非UI线程中实现消息(注意:我不是指Windows消息)?

我想要的是消息可以是对象或命令,比如Action<T> / Func<T>等。 我是否必须为不同类型的消息使用单独的队列?假设一个队列为对象,一个队列为Action<T> / Func<T>

鉴于消息类型有所不同,如何实现呢?

修改

我想要完成的是生产者/消费者模型,生产者/消费者共享队列进行通信,队列不仅可以包含供消费者使用的对象,还有一些'命令'可以被传递给消费者执行。

5 个答案:

答案 0 :(得分:2)

术语“消息泵”是指具体到Windows中的GUI线程(即使它们实际上并不创建GUI)。

听起来好像你在谈论消息队列 ,但即使是消息队列也不会接受动作功能,它将接受特定种类的消息

我当然从未听说过将Func<T>传递给消息队列 - 结果会怎么做?你打算把它存放在某个地方吗?将消息发回给呼叫者?这对我来说似乎没什么用。

无论是什么,你试图做的声音都会被内置的.NET线程池更好地处理。这实际上是为异步执行代码而设计的,而不是排队和处理消息

答案 1 :(得分:1)

好吧,你需要Thread.SetApartmentState()在启动之前将Thread切换到STA,然后在线程函数中调用Application.Run()来启动消息循环。

这与Action和Func代理无关。消息队列是一种内部Windows结构,用于存储鼠标和键盘消息。它不适合存储您自己的对象。不知道你想去哪里。


常规的System.Collections.Generic.Queue&lt;&gt; object可以存储任何类型的对象,包括表示“命令”的类的对象。使用ReaderWriterLockSlim使其成为线程安全的。

答案 2 :(得分:1)

我对你的最终目标感到有点困惑,但看起来你正在寻找两件事。

首先,在你想要传递对象的情况下(可能是你想要一个用于跨线程通信的消息队列?)听起来你想要一个pub / sub类型的实现?这有很好的记录,C#中有很多例子

对于第二种情况,你希望传递一个委托作为消息的有效负载,我猜你正试图在发布者和订阅者之间实现某种双向通信?像回调一样?

这是我感到困惑的地方。这里的问题究竟是什么?您可以实现一个了解如何处理不同消息有效内容类型的消息队列。类似于BroadcastMessage的东西,其中T是第一种情况的对象,第二种情况是委托(Func / action)。

我有一个codeplex项目,它是一个消息队列的简单实现,我在MVC / MVVM应用程序中用于特定目的。不确定这是你在找什么,但它可能会帮助你进一步澄清你的问题?

http://courier.codeplex.com/

答案 3 :(得分:1)

使其成为代码格式的单独答案

好的,所以在阅读你的更新之后,我想你想要我在“你想要的第二种情况”中描述的内容

Broadcast<T>("Foo") 

其中T是代表。

然后你的消费者会做

Subscribe<T>("Foo",HandlerMethod)

因此,生产者消费者场景看起来像这样

internal static class MessagePump
    {

        public static void Subscribe<T>(String foo, Action<String> handlerMethod)
        {
            throw new NotImplementedException();
        }

        public static void BroadcastMessage<T>(String foo, Action<String> someAction)
        {
            throw new NotImplementedException();
        }
    }

    public class Producer
    {
        void SendMessage()
        {
            MessagePump.BroadcastMessage<Action<String>>("Foo", SomeAction);
        }

        void SomeAction(String param)
        {
            //Do Something
        }
    }


    public class Consumer
    {

        public Consumer()
        {
            MessagePump.Subscribe<Action<String>>("Foo", HandlerMethod);
        }

        void HandlerMethod(String param)
        {
            // Do Something
        }

    }

这只是我头脑中的一个问题,是一个人为的例子,所以要带上一粒盐。这几乎就是我在之前发布的快递框架中所做的。您可能希望深入了解该代码以获得更具体的实现示例。

您需要考虑如何管理消费者,如何验证广播和订阅以及您的具体情况如何确保您传递的代理被正确调用?或者你在乎吗?

这有帮助吗?

答案 4 :(得分:1)

基本消息队列的示例

我想分享一下我曾为自己的研究写过的基本消息队列。这可能有助于了解可能的消息队列实现。没有声称完整性,完整性或没有错误,并且在大多数情况下可能存在比使用自定义消息队列更好的解决方案。

public void run()
{
    while (running)
    {
        mainLoopWaitHandle.WaitOne();

        EventHandlerFunction f = null;
        while (running)
        {
            f = popEvent();
            if (f == null) break;

            f();
        }
    }
}

private void pushEvent(EventHandlerFunction handlerFunction)
{
    lock (eventQueueLock)
    {
        int b = (queueInIndex + 1) & 255;
        if (b == queueOutIndex)
        {
            throw new Exception("Buffer overflow in event queue.");
        }

        eventQueue[queueInIndex] = handlerFunction;
        queueInIndex = b;

        mainLoopWaitHandle.Set();
    }
}


private EventHandlerFunction popEvent()
{
    EventHandlerFunction ret = null;

    lock(eventQueueLock)
    {
        int b = (queueOutIndex + 1) & 255;

        if (queueOutIndex == queueInIndex)
        {
            mainLoopWaitHandle.Reset();
            return null;
        }

        ret = eventQueue[queueOutIndex];
        eventQueue[queueOutIndex] = null;
        queueOutIndex = b;
    }

    return ret;
}

主线程通过运行run()开始使用消息队列。 run()是一种阻止方法,在类属性running设置为false之前不会返回。这可以通过下面解释的调用方法来完成。

要在主线程上调用方法,实际上需要两种方法。实际上在主线程上调用的一个EventHandlerFunction(让&#39; s说方法A())和在调用者线程上执行的方法B()。我认为这类似于用户界面的功能,其中B()是表单的一种方法,A()Invoke中获取B() d。

B()通过调用

调用A()
pushEvent(A);

pushEvent()popEvent()是线程保存。

方法B()的目的是在某些数据结构上存储任何对象或参数以进行数据传输,它代表方法A()的参数(或todo&#39; s)。例如,该结构可以是具有工作项的List<>。方法A()和B()都需要妥善锁定这种结构以保证线程安全。

方法A()还应该考虑缓冲区可以在消息队列或它自己的数据传输结构上运行,并且需要关心后果(例如,丢弃调用或阻塞)通话直到堆栈上有空间。)

希望它有所帮助。欢迎捐款。