基于AO的线程间通信

Cross-thread messaging with CActive objects 
基于AO的线程间通信

 

9 Dec 2008 - 11:09 
Keywords : Thread Program flow CActive 

Its been (literally) years since my last contribution here on newlc.com,
so I figured its time to get off my behind and share something again.

很久没有写点东西了,现在来分享点东西。

 The following is not a new idea by any standards,
 but having come across a working implementation for it some time ago,
I found it elegant and useful enough to improve it further and present it here.

下面不是什么新鲜玩意,是我之前遇到的一个工作实现,我发现他足够优雅和有用,所以抛砖引玉。

Now, the explicit use of threads is often discouraged in Symbian,
and even if they should be used, the client-server framework wrapped around the server thread
 is in most cases more than enough to provide inter-thread communication.

现在,明目张胆的在SYMBIAN中使用线程是不被鼓励的。
即使应该使用它们,包裹在服务线程之外的客户端-服务器框架,在多数情况之下,都能够搞定线程间的通信。

However here I try to present a solution to a problem where there are 2 equal producer/consumer threads
that require two-way communication not easily available via client-server framework.
There seem to be quite a few threads on the forums dealing with issues related to threads and active objects
 -- hopefully this writeup will provide new tools for people.

在这里,我想给出一个问题的解决办法:当有2个平等的 生产者/消费者 线程需要进行双向通信时,通过客户机-服务器框架不容易实现。
看来论坛上有关于线程和AO的讨论有不少 --- 但愿这点东西能提供给大家新的工具。


Say we have threads A (the consumer) and B (the producer).
 They communicate via a synchronized buffer. The buffer implementation specifics aren't central for the scope of this article.
 The buffer's data insertion method blocks the calling thread when there is no room in the buffer.

我们有线程A(消费者)和线程B(生产者),他们借助一个同步的BUFFER进行通信。
BUFFER的实现细节不是这里的重点,如果BUFFER没有空间了,BUFFER数据插入方法的调用者线程将被阻塞住。

That monitor is signaled every time the buffer's data retrieval method is called,
or a special Reset() method is called on the buffer. Now on to the beef of this presentation..

每次BUFFER数据取出方法被调用 或者 一个特殊的Reset()方法在BUFFER上调用时,MONITOR将接到通知。
下面是精华部分:

For sending messages between the two threads, we define the following class (ill omit anything obvious for clarity):

为了在两个线程之间传递消息,我们定义下面的类(为了清晰起见省略了很多)

typedef TInt (*ThreadCallback)(  TInt aMessage, TAny* aParam );

class CThreadMessager : public CActive
    {
    public: // New methods
        TInt SendMessage( TInt aMessage, TAny* aParam ); 
               
    private: // Constructors
            CThreadMessager ( ThreadCallback aCallback );
           
    private: // Data
        ThreadCallback iCallback; // 线程回调函数
        TThreadId iNotifyThreadId; //
        RSemaphore iSemaphore;  // 信号量
        RCriticalSection iCritSect; // 临界区
       
        // call data 调用数据
        TInt iMessage;  // 消息
        TAny* iParam;  // 消息参数
        TInt iReturnValue; // 返回值
    };

The important bits of the implementation are:
实现中的重点:


CThreadMessager::CThreadMessager ( ThreadCallback aCallback )
    : CActive( EPriorityStandard ),
      iCallback( aCallback )
    {
    CActiveScheduler::Add( this );
   
    // save the thread id of the current thread (RThread() creates a handle
    // to the currently executing thread)
 // 保存当前的线程ID

    iNotifyThreadId = RThread().Id();
    }

TInt CThreadMessager::SendMessage( TInt aMessage, 
                                          TAny* aParam )
    {
    // wait for mutex entry -- will be signaled in RunL()
 // 等待MUTEX入口 -- 在RunL()中会通知
    iCritSect.Wait(); // 等待进入临界区
   
    // save message values
 // 保存消息
    iMessage = aMessage;
    iParam = aParam;
    iReturnValue = KErrNone;
   
    if ( IsActive() )
        {
        Cancel();
        }
   
    RThread notifyThread;
    TInt ret = notifyThread.Open( iNotifyThreadId ); // 访问NOTIFY线程
    if ( ret != KErrNone )
        {
        // could not open a handle to the thread to be notified
 // 不能打开需要通知的线程
        iCritSect.Signal(); // 离开临界区
        return ret;       
        }
   
    // prepare the active object to make a call to RunL()
    SetActive();
   
    iStatus = KRequestPending;
    TRequestStatus* status = &iStatus;
    notifyThread.RequestComplete( status, KErrNone );
    notifyThread.Close();
   
     // wait till the return value is updated
     iSemaphore.Wait();
   
    return iReturnValue;
    }

void CThreadMessager::RunL()
    {
    // take a local copy of the call parameters
 //得到本地调用参数的COPY
    TInt message = iMessage;
    TAny* param = iParam;
   
    // release mutex, allowing next thread into SendMessage()
 //释放MUTEX
    iCritSect.Signal();
   
    // call the callback
    iReturnValue = iCallback( message, param );
   
    // release the thread waiting in SendMessage()
 // 释放SendMessage中的线程等待
    iSemaphore.Signal();
    }

So what does this thing do, then?

那么,这堆东西是干嘛的?

First, the construction. When it is constructed, it stores the current thread's id
 and adds itself to the current threads active scheduler (this is important).

首先,构造。当构造完成,他保存当前线程的ID,并将自己添加到当前线程的AO调度器(这很重要)。

This is why these objects need to be constructed in the thread they are to signal.

这就是为什么 这些对象需要在他们要通知的线程中构造的原因。

 Usual place in code would be in the beginning of the thread function,
after trap harness and active scheduler have been installed.

它(对象的构造的代码)一般的位置是在线程函数的开始,在TRAP处理和AO调度器安装之后。

The thread(s) should be be created with shared heap, as such for example:

线程应该被创建于共享堆,比如下面的例子:

    RThread thread;
    User::LeaveIfError( thread.Create( KThreadAName,
                                       CEncapsulatingClass::ThreadFunctionA,
                                       KThreadStackSize, NULL, this ) );

Second, the message passing. Say you have constructed an object of this type in thread A like this:
其次,消息传递。你在线程A中构造了这样的对象:

iThreadAMessager = CThreadMessager::NewL( CEncapsulatingClass::ThreadACallback );

Then, to pass message from any thread to thread A you would use:
然后,传递从任何线程传递消息到线程A 你会使用:

iThreadAMessager->SendMessage( EMyCustomMessage, this );

What happens is SendMessage will call SetActive() and notifyThread.RequestComplete() to trigger RunL() being run.
The thread switch occurs here (RunL() gets run in thread A instead of the thread calling SendMessage().).

接下来会发生的是,SendMessage 会调用 SetActive() 和 notifyThread.RequestComplete() 来触发 RunL()开始运行。
线程切换在这里发生。(RunL()在线程A中运行,而不是线程调用SendMessage())

SendMessage() will then wait against a semaphore until RunL() has been run and signals it, allowing SendMessage() to return.

SendMessage()会等待一个SEMAPHORE 直到RunL已经运行,并通知了他,允许SendMessage() 返回。

 This way you can pass return values from the thread A to the caller. To make this process asynhronous, just remove the blocking/signaling,
but then you'll lose the return value support.

用这种方式,你可以从线程A传值到调用者。要异步实现的话,只要去除 阻塞/通知 语句,但是那样做,你将没办法得到返回值。

And for the last thing, you'll need to define a callback function that gets called from the RunL():

最后,你需要定义一个回调函数给RunL调用。


void CEncapsulatingClass::ThreadACallback( TInt aMessage, TAny* aParam )
    {
    CEncapsulatingClass* self = reinterpret_cast<CEncapsulatingClass*>(aParam);
    switch ( aMessage )
        {
       case EMyCustomMessage:
           //TODO: handling code
           break;
       }
   }

 

In conclusion, the above mechanism is a simple approach to CActive-based communication between threads.

总之,上面的机制是基于AO的线程通信的一个简单方法。

It offers a thread-safe (thanks to the RCriticalSections) way of delivering messages from one thread to another and is not limited to
a one-way thinking like client-server architecture is -- which, although, is best suited for most cases.

他提供一个线程安全的方式,从一个线程向另一个线程发送消息,同时也不限用于单向方式如客户机-服务器架构--虽然,这是适用于大多数情况的。

The pitfalls include lengthy calculations in the signaled thread --
obviously RunL() cannot be triggered before the previous invocation has returned.

容易出错的地方包括 在被通知线程中进行冗长的计算,RunL()显然不能在前一次请求返回之前被触发。

In my example this will be implemented through the synchronized buffer;
if data producer thread B needs to be interrupted, the data buffer's Reset()
will be called which will signal thread B, letting it return from RunL(),
allowing the next message to go through.

在我的例子中,这将通过同步BUFFER来实现,如果数据生产者线程B需要被打断,数据BUFFER 的Reset()函数将被调用,这将通知线程B,让他从RunL()返回,从而允许下一个消息通过。

Tutorial posted December 9th, 2008 by mdahlbom 
 

 

Submitted by alh on Wed, 2008-12-10 12:54.

Looks nice.

看上去不错。

One thought though, isn't this quite similar to how RMsgQueue already works?

只是个想法,这不是和RMsgQueue 的工作方式很像吗?

You could limit the data send through the RMsgQueue to just an integer and a pointer, and you have quite similar functionality right?

你能够通过RMsgQuere限制仅仅发送一个int和一个指针数据,你也有很类似的功能,不是吗?

RMsgQueue can also be used to send data between threads in different processes,
but in that case you ofcourse have to copy also all argument data across the process boundary.

RMsgQuere也能用于不同进程的线程间发送数据,但是那种情况下,你必须跨越进程边界拷贝所有参数数据。

edit: well one difference, you still need to implement the active object handling the queue

一个区别是,你必须实现AO来处理消息队列

 
Login or register to post comments 
 

 

Submitted by mdahlbom on Wed, 2008-12-10 15:20.

I have to confess I have not looked much into RMsgQueue, but yeah it looks like its doing the same thing -- NotifyDataAvailable()+Receive() should do the job nicely.
 我承认我对RMsgQuere了解不深,但是他看起来确实在做同样的事情 -- NotifyDataAvailable()+Receive() 应该能很好的工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值