BLooper

Derived from: public BHandler

Declared in: be/app/Looper.h

Library: libbe.so


Overview

[method summary]

A BLooper object creates a "message loop" that receives messages that are sent or posted to the BLooper. The message loop runs in a separate thread that's spawned (and told to run) when the BLooper receives a Run() call. If you're creating your own BLooper, you can invoke Run() from within the constructor.

You tell the loop to stop by sending the BLooper a B_QUIT_REQUESTED message, which invokes the object's Quit() function. You can also call Quit() directly, but you have to Lock() the object first (BLooper locking is explained later). Quit() deletes the BLooper for you.

The BApplication class, the most important BLooper subclass, bends the above description in a couple of ways:


Messages and Handlers

You can deliver messages to a BLooper's thread by...

As messages arrive, they're added to the BLooper's BMessageQueue object. The BLooper takes messages from the queue in the order that they arrived, and calls DispatchMessage() for each one. DispatchMessage() locks the BLooper and then hands the message to a BHandler object by invoking the handler's MessageReceived() function. But which BHandler does the BLooper hand the message to? Here's the path:

After the handler is finished (when its MessageReceived() returns), the BMessage is automatically deleted and the BLooper is unlocked.


Locking

Access to many BLooper functions (and some BHandler functions) is proteced by a lock. To invoke a lock-protected functions (or groups of functions), you must first call Lock(), and then call Unlock() when you're done. The lock is scoped to the calling thread: Lock()/Unlock() calls can be nested within the thread. Keep in mind that each Lock() must balanced by an Unlock().

The BLooper constructor automatically locks the object. It's unlocked when Run() is invoked. This means that the Run() function--and any other lock-protected functions that you call before you call Run()--must be called from the thread that constructed the BLooper.


Allocation

Because they delete themselves when told to quit, BLoopers can't be allocated on the stack; you have to construct them with new.


Hook Functions

DispatchMessage()
Passes incoming messages to a BHandler; can be overridden to change the way certain messages or classes of messages are dispatched.

QuitRequested()
Can be implemented to decide whether a request to terminate the message loop and destroy the BLooper should be honored or not.


Constructor and Destructor


BLooper()

      BLooper(const char *name = NULL,
         int32 priority = B_NORMAL_PRIORITY, 
         int32 portCapacity = B_LOOPER_PORT_DEFAULT_CAPACITY) 
      BLooper(BMessage *archive) 

Assigns the BLooper object a name and then locks it (by calling Lock()). priority is a value that describes the amount of CPU attention the message loop will receive once it starts running, and portCapacity is the number of messages the BLooper can hold in its "message port" (this is not the message queue, as explained below).

After you construct the BLooper, you have to tell it to Run(). Because the object is locked, Run() can only be called from the thread that constructed the object. It's legal to invoke Run() from within a subclass implementation of the constructor.

Priority

A set of priority values are defined in kernel/OS.h; from lowest to highest, they are:

B_LOW_PRIORITY For threads running in the background that shouldn't interrupt other threads.
B_NORMAL_PRIORITY For all ordinary threads, including the main thread.
B_DISPLAY_PRIORITY For threads associated with objects in the user interface, including window threads.
B_URGENT_DISPLAY_PRIORITY For interface threads that deserve more attention than ordinary windows.
B_REAL_TIME_DISPLAY_PRIORITY For threads that animate the on-screen display.
B_URGENT_PRIORITY For threads performing time-critical computations.
B_REAL_TIME_PRIORITY For threads controlling real-time processes that need unfettered access to the CPUs.

Port Capacity

Messages that are sent to a BLooper first show up in a port (as the term is defined by the Kernel Kit), and then are moved to the BMessageQueue. The capacity of the BMessageQueue is virtually unlimited; the capacity of the port is not. Although messages are moved from the port to the queue as quickly as possible, the port can fill up. A full port will block subsequent message senders.

The default port capacity (100), should be sufficient for most apps, but you can fiddle with it through the portCapacity argument.


~BLooper()

      virtual ~BLooper()

Frees the message queue and all pending messages and deletes the message loop. BHandlers that have been added to the BLooper are not deleted, but BMessageFilter objects added as common filters are

In general, you should never delete your BLooper objects: With the exception of the BApplication object, BLoopers are destroyed by the Quit() function.


Static Functions


Instantiate()

      static BLooper *Instantiate(BMessage *archive) 

Returns a new BLooper object, allocated by new and created with the version of the constructor that takes a BMessage archive. However, if the archive message doesn't contain data for a BLooper object, this function returns NULL.

See also: BArchivable::Instantiate(), instantiate_object(), Archive()


LooperForThread()

      static BLooper *LooperForThread(thread_id thread)

Returns the BLooper object that spawned the specified thread, or NULL if the thread doesn't belong to a BLooper.


Member Functions


AddCommonFilter() see SetCommonFilterList()


AddHandler(), RemoveHandler(), HandlerAt(), CountHandlers(), IndexOf()

      void AddHandler(BHandler *handler)
      bool RemoveHandler(BHandler *handler)
      BHandler *HandlerAt(int32 index) const
      int32 CountHandlers(void) const
      int32 IndexOf(BHandler *handler) const 

AddHandler() adds handler to the BLooper's list of BHandler objects, and RemoveHandler() removes it. Only BHandlers that have been added to the list are eligible to respond to the messages the BLooper dispatches.

AddHandler() fails if the handler already belongs to a BLooper; a BHandler can belong to no more than one BLooper at a time. It can change its affiliation from time to time, but must be removed from one BLooper before it can be added to another. RemoveHandler() returns true if it succeeds in removing the BHandler from the BLooper, and false if not or if the handler doesn't belong to the BLooper in the first place.

AddHandler() also calls the handler's SetNextHandler() function to assign it the BLooper as its default next handler. RemoveHandler() calls the same function to set the handler's next handler to NULL.

HandlerAt() returns the BHandler object currently located at index in the BLooper's list of eligible handlers, or NULL if the index is out of range. Indices begin at 0 and there are no gaps in the list. CountHandlers() returns the number of objects currently in the list; the count should always be at least 1, since the list automatically includes the BLooper itself. IndexOf() returns the index of the specified handler, or B_ERROR if that object isn't in the list.

For any of these functions to work, the BLooper must be locked.

See also: BHandler::Looper(), BHandler::SetNextHandler(), PostMessage(), the BMessenger class


Archive()

      virtual status_t Archive(BMessage *archive, bool deep = true) const

Archives the BLooper by recording the priority of its thread and the capacity of its port in the BMessage archive passed as an argument.

See also: BArchivable::Archive(), Instantiate() static function


CommonFilterList() see SetCommonFilterList()


CountHandlers() see AddHandler()


CountLockRequests() see LockingThread()


CountLocks() see LockingThread()


CurrentMessage(), DetachCurrentMessage()

      BMessage *CurrentMessage(void) const
      BMessage *DetachCurrentMessage(void)

The message that a BLooper passes to its handler(s) is called the "current message." These functions access the current message; they're meaningless (they return NULL) when called from outside the message processing loop.

CurrentMessage() simply returns a pointer to the current message without affecting the BMessage object itself. This is particularly useful to functions that respond to system messages (such as MouseDown() and ScreenChanged()), but that aren't sent the full BMessage object that initiated the response.

DetachCurrentMessage() removes the current message from the message queue and passes ownership of it to the caller; deleting the message is the caller's responsibility. This is useful if you want to delay the response to the message without tying up the BLooper. But be careful--if the message sender is waiting for a synchronous reply, detaching the message and holding on to it will block the sender.


DetachCurrentMessage() see CurrentMessage()


DispatchMessage()

      virtual void DispatchMessage(BMessage *message, BHandler *target)

Dispatches messages as they're received by the BLooper's thread. Precisely how they're dispatched depends on the message and the designated target BHandler. The BWindow and BApplication classes that derive from BLooper implement their own versions of this function to provide for special dispatching of system messages. Each class defines its own set of such messages.

The target may be the BHandler object that was named when the message was posted, the handler that was specified when the BMessenger was constructed, the current preferred handler, the handler that was designated as the target for a reply message, or (for a BWindow) the BView where the message was dropped. It might be the BLooper itself, acting as a specific target or in its capacity as the default preferred handler. For system messages the target may be NULL; if so, the dispatcher must figure out a target for the message based on the contents of the BMessage object.

DispatchMessage() is the first stop in the message-handling mechanism. The BLooper's thread calls it automatically as it reads messages from the queue--you never call it yourself.

BLooper's version of DispatchMessage() dispatches B_QUIT_REQUESTED messages by calling its own QuitRequested() function, but only if the message is targeted to the BLooper itself. All other messages are forwarded to the target's MessageReceived() function. The BApplication and BWindow classes add other kinds of message-specific dispatching.

You can override this function to dispatch the messages that your own application defines or recognizes. Of course, you can also just wait for these messages to fall through to MessageReceived()--the choice is yours. If you do override DispatchMessage(), you should:

For example:

   void MyLooper::DispatchMessage(BMessage *msg, BHandler *target)
   {
       switch ( msg->what ) {
       case MY_MESSAGE1:
           . . .
           break; 
       case MY_MESSAGE2:
           . . .
           break; 
       default:
           baseClass::DispatchMessage(msg, target);
           break;
       }
   }

Don't delete the messages you handle when you're through with them; they're deleted for you.

The system locks the BLooper before calling DispatchMessage() and keeps it locked for the duration of the thread's response to the message (until DispatchMessage() returns).

See also: the BMessage class, BHandler::MessageReceived(), QuitRequested()


HandlerAt() see AddHandler()


IndexOf() see AddHandler()


IsLocked() see LockingThread()


Lock(), LockWithTimeout(), Unlock()

      bool Lock(void)
      status_t LockWithTimeout(bigtime_t timeout)
      void Unlock(void)

These functions lock and unlock the BLooper. Only one thread can have the BLooper locked at any given time.

Lock() blocks until it can lock the object, then returns true. If the calling thread already has the object locked, it returns true immediately. If another thread has the BLooper locked, it waits until that thread releases the lock and it can acquire it; it then returns true. It returns false if the BLooper is destroyed while you're waiting for Lock() to return.

LockWithTimeout() lets you set a limit (in microseconds) on the amount of time you want the BLooper to block while waiting for the lock. If the time limit expires, the function returns B_TIMED_OUT. If the timeout is 0, it doesn't block but returns immediately with or without the lock. If the timeout is B_INFINITE_TIMEOUT, it blocks without limit, just as Lock() does.

If it locks the BLooper (or if the calling thread already has it locked), LockWithTimeout() returns B_OK. In addition to B_TIMED_OUT, it may also return B_BAD_VALUE if the BLooper has been deleted, is invalid, or was improperly allocated. Each of these failures would cause Lock() to return false.

Note that if Lock() returns 0 (false), it has failed to lock the BLooper, but if LockWithTimeout() returns 0 (B_OK), it has succeeded.

Unlock() releases the lock previously obtained by Lock() or LockWithTimeout(). Only the locking thread should call Unlock(). This is the natural result if it's called in the same section of code to balance a previous Lock() or LockWithTimeout() call, as follows:

   if ( myLooper->Lock() ) {
       myLooper->DoSomethingCritical();
       . . .
       myLooper->Unlock();
   }

Calls to Lock() (or LockWithTimeout()) and Unlock() can be nested. For example, the function that's called within the brace of the lock in the example above can itself call Lock() and Unlock():

   status_t MyLooperClass::DoSomethingCritical(void)
   {
       if ( Lock() ) {
           . . .
           Unlock();
           return B_OK;
       }
       return B_ERROR;
   }

If the locking functions are called more than once from the same thread, it will take an equal number of Unlock() calls from that thread to unlock the BLooper. Only when Unlock() has released the lock at the base level will another thread be permitted to lock the BLooper.

It's generally not necessary to lock a BLooper when calling functions defined in the class itself or in a derived class. The BLooper is locked for you when:

Moreover, BApplication and BWindow functions are implemented to call Lock() and Unlock() when necessary. Functions you define in classes derived from BLooper (or from BApplication and BWindow) should also call Lock() (or LockWithTimeout()) and Unlock().

See also: LockingThread(), BMessenger::LockTarget(), BHandler::LockLooper(), the BLocker class in the Support Kit


LockingThread(), IsLocked(), CountLocks(), CountLockRequests(), Sem()

      thread_id LockingThread(void) const
      bool IsLocked(void) const
      int32 CountLocks(void) const
      int32 CountLockRequests(void) const
      sem_id Sem(void) const

These functions may be useful while debugging a BLooper.

LockingThread() returns the thread that currently has the BLooper locked, or -1 if the BLooper isn't locked.

IsLocked() returns true if the calling thread currently has the BLooper locked (if it's the locking thread) and false if not (if some other thread is the locking thread or the BLooper isn't locked).

CountLocks() returns the number of times the locking thread has locked the BLooper--the number of Lock() (or LockWithTimeout()) calls that have not yet been balanced by matching Unlock() calls.

CountLockRequests() returns the number of threads currently trying to lock the BLooper. The count includes the thread that currently has the lock plus all threads currently waiting to acquire it.

Sem() returns the sem_id for the semaphore that the BLooper uses to implement the locking mechanism.

See also: Lock()


LockWithTimeout() see Lock()


MessageReceived()

      virtual void MessageReceived(BMessage *message)

Simply calls the inherited function. For the current release, the BLooper implementation of this function does nothing of importance.

See also: BHandler::MessageReceived()


MessageQueue()

      BMessageQueue *MessageQueue(void) const

Returns the queue that holds messages delivered to the BLooper's thread. You rarely need to examine the message queue directly; it's made available so you can cheat fate by looking ahead.

See also: the BMessageQueue class


PostMessage()

      status_t PostMessage(BMessage *message, 
         BHandler *handler, 
         BHandler *replyHandler = NULL)
      status_t PostMessage(uint32 command, 
         BHandler *handler, 
         BHandler *replyHandler = NULL)
      status_t PostMessage(BMessage *message)
      status_t PostMessage(uint32 command)

Delivers a message to the BLooper, just as constructing a BMessenger and calling SendMessage() would.

If a target handler object is named for the message, it will be passed as the designated handler to DispatchMessage(). DispatchMessage() will, in turn, call an appropriate function of the handler to respond to the message. However, if the target BHandler isn't associated with the BLooper (if the handler's Looper() function returns NULL or some other BLooper object), the posting fails. A BHandler must be associated with a BLooper before it can be the target for dispatched messages; it can't get messages from any other BLooper except the one to which it belongs. For example, BViews in the Interface Kit can receive messages only from the BWindows to which they're attached.

If the handler is NULL, the designated handler will be the BLooper's preferred handler at the time DispatchMessage() is called.

For the versions of PostMessage() that take a single argument and don't allow you to designate a handler, the handler will be the BLooper object. These shorthand functions may not be supported in the future. It's better programming practice to name the BLooper explicitly as the target handler.

Replies to the posted message will be delivered to the replyHandler BHandler. Like the target handler, this object must belong to a BLooper (not necessarily this BLooper) or be a BLooper itself. If a replyHandler isn't specified, replies will be delivered to the BApplication object.

The caller retains ownership of the posted message; it's safe to delete it when PostMessage() returns.

If a command is passed rather than a message, PostMessage() creates a BMessage object, initializes its what data member to command, and posts it. This simply saves you the step of constructing a BMessage when it won't contain any data. For example, this code

   myWindow->PostMessage(command, handler);

is equivalent to:

   BMessage message(command);
   myWindow->PostMessage(&message, handler);

PostMessage() returns B_OK if successful, B_MISMATCHED_VALUES if the posting fails because the proposed target BHandler doesn't belong to the BLooper, B_WOULD_BLOCK if there are too many messages already pending (in which case you might try to snooze for a while and try sending the message again), and B_ERROR, B_BAD_PORT_ID, or some other error if it fails because the BLooper is invalid or corrupted.

See also: BHandler::Looper(), DispatchMessage()


PreferredHandler() see SetPreferredHandler()


Quit()

      virtual void Quit(void)

Shuts down and deletes the BLooper; the object must be locked for this to succeed.

If Run() hasn't been called yet, Quit() just deletes the BLooper object. But if Run() has been called, it exits the message loop, frees the message queue, kills the thread, and then deletes the BLooper object.

When Quit() is called from the BLooper's thread, all this happens immediately. Any pending messages are ignored and destroyed. Because the thread dies, Quit() doesn't return.

However, when called from another thread, Quit() waits until all previously posted messages (all messages already in the queue) work their way through the message loop and are handled. It then destroys the BLooper and returns only after the loop, queue, thread, and object no longer exist.

Quit() therefore terminates the BLooper synchronously; when it returns, you know that everything has been destroyed. To quit the BLooper asynchronously, you can post a B_QUIT_REQUESTED message to the thread (that is, a BMessage with B_QUIT_REQUESTED as its what data member). PostMessage() places the message in the queue and returns immediately.

When it gets a B_QUIT_REQUESTED message, the BLooper calls the QuitRequested() virtual function. If QuitRequested() returns true, as it does by default, it then calls Quit().

See also: QuitRequested()


QuitRequested()

      virtual bool QuitRequested(void)

Implemented by derived classes to determine whether the BLooper should quit when requested to do so. The BLooper calls this function to respond to B_QUIT_REQUESTED messages. If it returns true, the BLooper calls Quit() to exit the message loop, kill the thread, and delete itself. If it returns false, the request is denied and no further action is taken.

BLooper's default implementation of QuitRequested() always returns true.

A request to quit that's delivered to the BApplication object is, in fact, a request to quit the entire application, not just one thread. BApplication therefore overrides QuitRequested() to pass the request on to each window thread before shutting down.

For BWindow objects in the Interface Kit, a request to quit might come from the user clicking the window's close button (a quit-requested event for the window), from the user's decision to quit the application (a quit-requested event for the application), from a Close menu item, or from some other occurrence that forces the window to close.

Classes derived from BWindow typically implement QuitRequested() to give the user a chance to save documents before the window is destroyed, or to cancel the request.

If a BWindow represents the last window the application has open (or the last one that gives the user access to menus and the ability to continue doing work), closing the window is tantamount to quitting the application. In this case, QuitRequested() should make sure the application quits by passing the request along to the BApplication object. For example

   bool MyWindow::QuitRequested()
   {
       . . .
       if ( myDocuments <= 1 )
           be_app->PostMessage(B_QUIT_REQUESTED, be_app);
       return true;
   }

After asking the application to quit, QuitRequested() returns true to immediately dispose of the window. If it returns false, BApplication's version of the function will again request the window to quit.

If you call QuitRequested() from your own code, be sure to also provide the code that calls Quit():

   if ( myLooper->QuitRequested() )
       myLooper->Quit();

See also: BApplication::QuitRequested(), Quit()


RemoveCommonFilter() see SetCommonFilterList()


Run()

      virtual thread_id Run(void)

Spawns a thread at the priority level that was specified when the BLooper was constructed and begins running a message loop in that thread. If successful, this function returns the thread identifier. If unsuccessful, it returns B_NO_MORE_THREADS or B_NO_MEMORY to indicate why.

Run() expects the BLooper to be locked when it's called--and to be locked just once. Since a BLooper is locked on construction, you should not lock it again before calling Run(). Run() will unlock the BLooper, but make sure that it's locked while the thread responds to each dispatched message.

A BLooper can be run only once. If called a second time, Run() returns B_ERROR, but doesn't disrupt the message loop already running. (Currently, it drops into the debugger so you can correct the error.)

The message loop is terminated when Quit() is called, or (potentially) when a B_QUIT_REQUESTED message is received. This also kills the thread and deletes the BLooper object.

See also: the BLooper constructor, the BApplication class, Quit()


SetCommonFilterList(), CommonFilterList(), AddCommonFilter(), RemoveCommonFilter()

      virtual void SetCommonFilterList(BList *list)
      BList *CommonFilterList(void) const
      virtual void AddCommonFilter(BMessageFilter *filter)
      virtual void RemoveCommonFilter(BMessageFilter *filter)

These functions manage a list of filters that can apply to any message the BLooper receives, regardless of its target BHandler. They complement a similar set of functions defined in the BHandler class. When a filter is associated with a BHandler, it applies only to messages targeted to that BHandler. When it's associated with a BLooper as a common filter, it applies to all messages that the BLooper dispatches, regardless of the target.

In addition to the list of common filters, a BLooper can maintain a filter list in its role as a BHandler. These filters apply only if the BLooper is the target of the message (see SetFilterList() in the BHandler class.)

SetCommonFilterList() assigns the BLooper a new list of common filters; the list must contain pointers to instances of the BMessageFilter class or instances of classes that derive from BMessageFilter. The new list replaces any list of common filters previously assigned. All objects in the previous list are deleted, as is the BList itself. If list is NULL, the current list is removed without a replacement. CommonFilterList() returns the current list of common filters.

AddCommonFilter() adds a filter to the end of the list of common filters. It creates the BList object if it doesn't already exist. By default, BLoopers don't keep a BList of common filters until one is assigned or AddCommonFilter() is called for the first time. RemoveCommonFilter() removes a filter from the list without freeing it. It returns true if successful, and false if it can't find the specified filter in the list (or the list doesn't exist). It leaves the BList in place even after removing the last filter.

For SetCommonFilterList(), AddCommonFilter(), and RemoveCommonFilter() to work, the BLooper must be locked.

See also: BHandler::SetFilterList(), Lock(), the BMessageFilter class


SetPreferredHandler(), PreferredHandler()

      void SetPreferredHandler(BHandler *handler) const
      BHandler *PreferredHandler(void) 

These functions set and return the BLooper's preferred handler--the BHandler object that should handle messages not specifically targetted to another BHandler.

To designate the current preferred handler--whatever object that may be--as the target of a message, pass NULL for the target handler to PostMessage() or to the BMessenger constructor.

Posting or sending messages to the preferred handler can be useful. For example, in the Interface Kit, BWindow objects name the current focus view as the preferred handler. This makes it possible for other objects--such as BMenuItems and BButtons--to target messages to the BView that's currently in focus, without knowing what view that might be. For example, by posting its messages to the window's preferred handler, a Cut menu item can make sure that it always acts on whatever view contains the current selection. See the chapter on the Interface Kit for information on windows, views, and the role of the focus view.

By default, BLoopers don't have a preferred handler; until one is set, PreferredHandler() returns NULL. Note however, that messages targeted to the preferred handler are dispatched to the BLooper whenever the preferred handler is NULL. In other words, the BLooper acts as default preferred handler, even though the default is formally NULL.

See also: BControl::SetTarget() and BMenuItem::SetTarget() in the Interface Kit, PostMessage()


Thread(), Team()

      thread_id Thread(void) const
      team_id Team(void) const 

These functions identify the thread that runs the message loop and the team to which it belongs. Thread() returns B_ERROR if Run() hasn't yet been called to spawn the thread and begin the loop. Team() always returns the application's team_id.


Unlock() see Lock()






The Be Book, in lovely HTML, for BeOS Release 4.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified December 23, 1998.