Making cross-thread calls using runnables

In the Mozilla platform, most activities such as layout, DOM operations, content JavaScript, and chrome JavaScript run on the main thread. However, it may be useful for C++ code in the Mozilla platform to run tasks on another thread. Typically, thread activities are triggered and managed using an XPCOM event-passing framework that uses the nsIRunnable interface. Each runnable represents a task which can then be dispatched to another thread for execution.

Note: JavaScript code cannot use the techniques described in this article. Chrome JavaScript should instead use workers.

In general, threading and message passing should be asynchronous. For example, let's say we have a function, CalculatePi(int digits), which will calculate π to an arbitrary number of digits:

void CalculatePi(int digits, nsCString& result); // This is synchronous

This can take a while, so we don't want to run this on the main thread. Instead, we want to run it on a new thread and be notified when the result is available. So we declare an asynchronous version of the same function:

typedef void (*PiCallback)(const nsCString& result); // Callback function
void CalculatePiAsynchronously(int digits, PiCallback callback);

Creating a runnable

nsRunnable is a helper class: it already implements threadsafe refcounting, so all you need to do is override the Run() function. In our example, we actually need two runnables: one dispatched to the worker thread, and one to hand us back the result.

#include "nsThreadUtils.h"

class PiResultTask : public nsRunnable
{
public:
  PiResultTask(PiCallback callback, const nsACString& result)
    : mCallback(callback)
    , mResult(result)
    , mWorkerThread(do_GetCurrentThread())
  {
    MOZ_ASSERT(!NS_IsMainThread()); // This should be running on the worker thread
  }

  NS_IMETHOD Run() {
    MOZ_ASSERT(NS_IsMainThread()); // This method is supposed to run on the main thread!
    mCallback(mResult);

    // If we don't destroy the thread when we're done with it, it will hang around forever... bad!
    // But thread->Shutdown must be called from the main thread, not from the thread itself.
    mWorkerThread->Shutdown();
  }

private:
  PiCallback mCallback;
  nsCString mResult;
  nsCOMPtr<nsIThread> mWorkerThread;
};

class PiCalculateTask : public nsRunnable
{
public:
  PiCalculateTask(PiCallback callback, int digits)
    : mCallback(callback)
    , mDigits(digits)
  { }

  NS_IMETHOD Run() {
    nsCString result;
    CalculatePi(mDigits, result);
    nsCOMPtr<nsIRunnable> resultrunnable = new PiResultTask(mCallback, result);
    NS_DispatchToMainThread(resultrunnable);
  }

private:
  PiCallback mCallback;
  int mDigits;
};

Putting it all together

To start a new thread, create it using the Thread Manager:

#include "nsXPCOMCIDInternal.h"

void CalculatePiAsynchronously(int digits, PiCallback callback)
{
  // To create a new thread, get the thread manager
  nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
  nsCOMPtr<nsIThread> mythread;
  nsresult rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
  if (NS_FAILED(rv)) {
    // In case of failure, call back immediately with an empty string which indicates failure
    callback(EmptyCString());
    return;
  }

  nsCOMPtr<nsIRunnable> r = new PiCalculateTask(callback, digits);

  mythread->Dispatch(r, nsIEventTarget::DISPATCH_NORMAL);
  // The result callback will shut down the worker thread, we can let it go here...
}

See also