Attention : Le contenu de ces pages n'a pas été mis à jour depuis longtemps. Il est probablement obsolète pour Firefox 4.0/Gecko 4.0 et supérieur. Pour du contenu plus récent, allez consulter developer.mozilla.org.

Thread

Créer un thread

Pour créer un thread (ou "processus léger"), on peut utiliser le composant @mozilla.org/thread;1, qui permet d'exécuter dans un nouveau thread la méthode Run d'un composant implémentant l'interface nsIRunnable.

Voir ThreadJavascript pour un exemple en javascript.

Partager des objets entre plusieurs threads

En quelques phrases

Si P est un proxy créé dans/pour le thread T à partir de l'objet O, P permet d'invoquer toutes les méthodes de O, et garantit qu'elles seront exécutées dans le thread T.

Le premier usage habituel des proxies est de permettre d'appeler les methodes d'un objet O a partir du thread T (typiquement, le thread qui s'occupe de l'interface utilisateur) sans paralyser le thread T. Pour ce faire, la procedure consiste a

  • créer l'objet O
  • créer un thread secondaire U (a l'aide de nsIThread)
  • recuperer/créer la file d'évenements E de U (a l'aide de nsIEventQueueService)
  • utiliser E pour creer un proxy P sur O (a l'aide de nsIProxyObjectManager)
  • utiliser P a la place de O de maniere transparente. Quel que soit le thread appelant, chaque appel de methode sera execute dans U.

La technique fonctionne aussi bien en JavaScript qu'en C++. Dans l'exemple suivant, la constante speciale NS_CURRENT_EVENTQ sert a employer la file d'evenements du thread courant. Ce n'est generalement pas ce que vous desirez.

En détail

Nous décrirons ici comment appeler les méthodes d'un objet XPCOM à partir d'un thread différent du thread principal. On entend par thread principal d'une application mozilla, le thread lié à la gestion des évènements de l'interface utilisateur.

Pour effectuer sans problèmes des appels méthodes dans un environnement multithread, il faut "préparer" l'objet dont on veut appeler les méthodes, c'est ce que fait la classe "@mozilla.org/xpcomproxy;1" qui implémente la méthode GetProxyForObject via l'interface nsIProxyObjectManager :

 nsresult NS_GetProxyForObject( nsIEventQueue *destQueue,
                                REFNSIID aIID,
                                nsISupprts* aObj,
                                PRInt32 proxyType,
                                void** aProxyObject)
 {
   // Récupère le proxy manager sous forme de service
   nsresult rv;
   nsComPtr<nsIPproxyObjectManager> proxyObjMgr =
         do_GetService("@mozilla.org/xpcomproxy;1",&rv);
   if(NS_FAILED(rv))
      return rv;
   return proxyObjMngr->GetProxyForObject(destQueue,aIID,aObj,proxyType,aProxyObject);
 }

Remarque : Bien que possédant les mêmes méthodes, l'objet renvoyé est différent de l'objet initial passé en paramètre.

Les paramètres interessant sont:

  1. destQueue qui est un pointeur vers la file d'événements associé au thread correspondant au contexte d'exécution des méthodes appelées via notre objet proxy. Généralement on utilisera la valeur NS_CURRENT_EVENTQ qui spécifie que le contexte d'exécution est celui du thread dans lequel l'objet proxy est crée, i.e le thread principal.
  1. proxyType est un flag qui peut être la combinaison des valeurs suivantes (cf nsProxyEvent.h) : PROXY_SYNC : Comme un appel de fonction, le thread appelant attend le retour de la fonction. PROXY_ASYNC : Appel non bloquant, l'appel retourne immédiatement. PROXY_ALWAYS : Ne teste pas si destQueue appartient bien au même thread que celui où est appelé GetProxyForObject et retourne toujours un objet "proxy" (note: cette affirmation est à vérifier).
  1. aIID : l'IID de l'interface qui décrit les méthodes qui seront utilisées dans le thread appelant.

Dans l'exemple suivant on prépare un objet "aFoobar" qui implémente l'interface nsIFoobar pour être appelé de manière asynchrone (non bloquante) dans un environnement multithread:

 //aFoobar est un objet inaccessible dans un thread
 //On va cloner cet objet pour que plus tard il soit accessible
 //de n'import quel thread.
 nsComPtr<nsIFoobar> foobarProxy;
 nsresult rv = NS_GetProxyForObject(NS_CURRENT_EVENTQ,
                                     NS_GETIID(nsIFoobar),
                                     aFoobar,
                                     PROXY_ASYNC|PROXY_ALWAYS,
                                     getter_AddRefs(foobarProxy));
 //Donc dans un thread, si on veut manipuler aFoobar, on utilisera
 //plutot foobarProxy. On pourra considérer foobarProxy comme
 //la meme instance que aFoobar, mais accessible de n'importe
 //quel thread.

Voir aussi http://www.mozilla.org/projects/xpcom/Pr(..)

Exemple de thread

avec des propriétés proxy

Créer un composant nsIRunnable (on l'appelera par la suite runnable), en C++ si vous voulez communiquer entre thread, en particulier si vous voulez modifier le document de la fenêtre ou d'autres objets du thread principal.

Une chose qu'il est recommandé de faire : votre objet runnable devra implementer une autre interface, définissant des propriétés, ou une méthode init avec les arguments qu'il vous faut. Ces propriétés ou arguments seront en fait les objets du thread principal auquel vous voulez acceder à partir de votre nouveau thread. Ex :

 interface nsIMyInterface : nsISupports
 {
     attribute nsIDOMDocument mainDoc;
 }

ce qui donne en C++ :

 class myRunnable : public nsIMyInterface, nsIRunnable
 {
  public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
     NS_DECL_NSIMYINTERFACE
    myRunnable();
  private:
    ~myRunnable();
  protected:
     nsCOMPtr<nsIDOMDocument> mDocument;
     nsCOMPtr<nsIDOMDocument> mDocumentProxy;
 };

Vous aurez donc les methodes

  NS_IMETHODIMP myRunnable::Run(){...}
  NS_IMETHODIMP myRunnable::GetMainDoc(nsIDOMDocument ** aMainDoc){...}
  NS_IMETHODIMP myRunnable::SetMainDoc(nsIDOMDocument * aMainDoc){...}

Dans le setter SetMainDoc, il vous faudra "proxyfier" l'objet aMaindoc si vous voulez l'utiliser dans la méthode Run (donc dans votre nouveau thread).

Ça donne à peu prés ça :

 NS_IMETHODIMP myRunnable::SetMainDoc(nsIDOMDocument * aMainDoc){
  nsresult rv;
  mDocument = aMainDoc; // pour le getter
  rv = NS_GetProxyForObject(NS_CURRENT_EVENTQ,
                            nsIDOMDocument::GetIID(),
                            aMainDoc,
                            PROXY_SYNC|PROXY_ALWAYS,
                            getter_AddRefs(mDocumentProxy));
  return rv;
 }

Dans votre méthode run, vous pourrez ensuite utiliser mDocumentProxy pour accéder au document de votre fenêtre.

Avec des objets services

Une autre idée est aussi d'utiliser des observateurs (voir RessourcesLibs/Observers pour voir comment utiliser les observateurs), ou autre objet de type "service".

Dans ce cas, comme ce sont des services, il n'y a pas besoin de les passer en paramètre à votre composant runnable, donc pas besoin de créer une nouvelle interface comme précédement. Tout se fera dans le constructeur de votre objet myRunnable : vous récupérez le ou les objets service en questions, et vous les "proxifiez". Exemple avec le service d'observation :

 class myRunnable : public nsIRunnable
 {
  public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
    myRunnable();
  private:
    ~myRunnable();
  protected:
     nsCOMPtr<nsIObserverService> mObserverServiceProxy;
 };
 myRunnable::myRunnable()
  {
  nsresult rv;
  // récupération du service
  nsCOMPtr<nsIObserverService> observerService =
       do_GetService("@mozilla.org/observer-service;1", &rv);
  // proxy sur le service dans mObserverServiceProxy
  rv = NS_GetProxyForObject(NS_CURRENT_EVENTQ,
                            nsIObserverService::GetIID(),
                            observerService,
                            PROXY_SYNC|PROXY_ALWAYS,
                            getter_AddRefs(mObserverServiceProxy));
  }

Ensuite, dans la méthode run(), vous pouvez appeler l'observateur pour notifier de certains évènements :

  mObserverServiceProxy->NotifyObservers(nsnull, "hello", nsnull);

Du coté de votre thread principal, il vous faut bien sûr avoir ajouter un observateur qui réponde à cet évènement "hello" (ledit observateur pouvant alors modifier la fenètre, faire d'autres traitement etc..)

L'avantage d'utiliser des observateurs est que vous pouvez ajouter la génération/gestion d'évènement sans avoir à modifier l'interface de votre composant, contrairement à la méthode précédente.

Bien sûr, on peut tout à fait combiner les deux méthodes.

Créer un proxy en Javascript

La fonction suivante retourne un objet proxy pour l'objet xpcom aObject passé en paramètre:

 function getProxyOnUIThread(aObject, aInterface, aSync )
 {
   if(aObject)
   {
     const nsIProxyObjectManager = Components.interfaces.nsIProxyObjectManager;
     const nsIEventQueueService  = Components.interfaces.nsIEventQueueService  ;
     var eventQSvc = Components.classes["@mozilla.org/event-queue-service;1"].
                                            getService(nsIEventQueueService);
     var uiQueue = eventQSvc.getSpecialEventQueue(
                            nsIEventQueueService.UI_THREAD_EVENT_QUEUE);
     var proxyMgr = Components.
             classes["@mozilla.org/xpcomproxy;1"].getService(nsIProxyObjectManager);
     var proxyFlags = nsIProxyObjectManager.FORCE_PROXY_CREATION;
     proxyFlags |= (aSync ? nsIProxyObjectManager.INVOKE_ASYNC :
                            nsIProxyObjectManager.INVOKE_SYNC);
     return proxyMgr.getProxyForObject(uiQueue,aInterface, aObject, proxyFlags );
   }
   return null;
 }

Copyright © 2003-2013 association xulfr, 2013-2016 Laurent Jouanneau - Informations légales.

Mozilla® est une marque déposée de la fondation Mozilla.
Mozilla.org™, Firefox™, Thunderbird™, Mozilla Suite™ et XUL™ sont des marques de la fondation Mozilla.