2009-06-03 6 views
2

La sémantique de verrouillage inhabituelle de .Net me perturbe à nouveau.C#: Attendre que la variable devienne non-nulle

Je lance un thread, le thread enfant à son tour commence un formulaire. Le thread parent doit attendre que le formulaire soit créé.

Ma première tentative a été d'utiliser un moniteur pour regarder la variable Forme:

private void OpenForm() 
{ 
    if (FormThread == null) 
    { 
     Monitor.Enter(Form); 
     FormThread = new Thread(FormStub); 
     FormThread.SetApartmentState(ApartmentState.STA); 
     FormThread.Start(); 
     Monitor.Wait(Form); 
     Monitor.Exit(Form); 
    } 
} 

private void FormStub() 
{ 
    Form = new ConnectorForm(); 
    Monitor.Enter(Form); 
    Monitor.PulseAll(Form); 
    Monitor.Exit(Form); 
    Application.Run(Form); 
} 

... Ce lève une exception. Monitor.Enter() échoue, puisque Form == null.

Je pourrais très facilement créer un nombre entier fictif ou quelque chose (je pense réellement que je vais canabaliser la variable FormThread), mais je me demandais s'il y avait une solution plus élégante.

Répondre

4

Une meilleure synchronisation primitive pour ce cas:

private ManualResetEvent mre = new ManualResetEvent(false); 

private void OpenForm() 
{ 
    if (FormThread == null) 
    { 
     FormThread = new Thread(FormStub); 
     FormThread.SetApartmentState(ApartmentState.STA); 
     FormThread.Start(); 
     mre.WaitOne(); 
    } 
} 

private void FormStub() 
{ 
    Form = new ConnectorForm(); 
    mre.Set(); 
    Application.Run(Form); 
} 
+0

Cela semble être exactement le primitif que je cherchais. Je l'ai manqué quand j'ai lu la liste des choses dans System.Threading, apparemment ... mes globes oculaires cherchaient "Event" ou quelque chose comme ça. – Thanatos

0

Ne pas effectuer un spin-wait sur le thread en cours supprimer tout le point d'utiliser un thread séparé pour lanch le nouveau formulaire? À moins d'avoir mal compris quelque chose, vous voulez juste créer le nouveau formulaire de manière synchrone. (Y at-il raison, il doit résider dans un autre STA?)

+0

On dirait qu'il veut courir sa boucle principale d'application de l'autre fil pour une raison quelconque ...? – jerryjvl

+0

Le problème mineur est qu'il me permet de ne pas se soucier du modèle de thread du thread principal, et si oui ou non il prend en charge les formulaires. Le plus grand avantage est que le thread principal récupère le contrôle, et peut effectuer des opérations comme s'il s'agissait d'un seul thread, alors que cette forme se trouve dans un autre thread et garde l'utilisateur diverti et informé. Mon espoir est que l'API de fin du thread principal est très agréable. – Thanatos

0

Vous pouvez essayer ce qui suit, qui utilise un seul object/Monitor comme mécanisme de message:

private void OpenForm() 
{ 
    if (FormThread == null) 
    { 
     object obj = new object(); 
     lock (obj) 
     { 
      FormThread = new Thread(delegate() { 
       lock (obj) 
       { 
        Form = new ControllerForm(); 
        Monitor.Pulse(obj); 
       } 
       Application.Run(Form); 
      }); 
      FormThread.SetApartmentState(ApartmentState.STA); 
      FormThread.Start(); 
      Monitor.Wait(obj); 
     } 
    } 
} 

Le fil d'origine détient le verrou jusqu'à ce qu'il appelle Monitor.Wait; cela permet au deuxième thread (déjà démarré) de créer le formulaire, de repousser le thread original dans la vie et de le relâcher; le thread d'origine n'existe ensuite qu'une fois que Form existe.

+0

Que se passe-t-il si 'Monitor.Pulse' est appelé entre' FromThread.Start' et 'Monitor.Wait'? – Dialecticus

+0

@Dialecticus garder à l'esprit: la seule personne qui peut appeler «Pulse» est la personne qui a la serrure. Et jusqu'à ce que vous appeliez 'Wait', ce verrou est en cours d'utilisation. Donc la réponse à cela est soit "ça ne peut pas être", ou plus explicitement: "le thread qui appelle' Monitor.Pulse' va recevoir une exception, car * ils n'ont pas le verrou * " –

0

J'ai tendance à utiliser le AutoResetEvent pour des cas comme ceux-ci:

private AutoResetEvent _waitHandle = new AutoResetEvent(false); 

private void OpenForm() 
{ 
    Thread formThread = new Thread(FormStub); 
    formThread.SetApartmentState(ApartmentState.STA); 
    formThread.Start(); 
    _waitHandle.WaitOne(); 

    // when you come here FormStub has signaled     
} 

private void FormStub() 
{ 
    // do the work 

    // signal that we are done 
    _waitHandle.Set(); 
} 
-1

Utilisez un bool statique pour signaler si le formulaire ou non chargée. Il est atomique, donc pas besoin de verrouillage.

Dans le code principal ne vient quelque chose comme

while(!formRun) { Thread.Sleep(100); } 

La vraie question est pourquoi fais-tu cela? Habituellement, vous voulez que le thread principal exécute des éléments graphiques et des threads secondaires pour exécuter le code auxiliaire. Si vous expliquez pourquoi vous en avez besoin, nous pouvons peut-être trouver une meilleure technique.

+0

Mieux vaut espérer que formRun est volatile, alors ... ce n'est pas garanti de sortir autrement. –

0

Une autre façon de passer un EventWaitHandle est de passer à FormStub comme paramètre (il n'encombre pas votre modèle d'objet):

static void Main() 
{ 
    Application.EnableVisualStyles(); 
    Application.SetCompatibleTextRenderingDefault(false); 

    EventWaitHandle e = new EventWaitHandle(false, EventResetMode.ManualReset); 
    Thread t = new Thread(FormStub); 
    t.SetApartmentState(ApartmentState.STA); 
    t.Start(e); 
    e.WaitOne(); 
} 

static void FormStub(object param) 
{ 
    EventWaitHandle e = (EventWaitHandle) param; 
    Form f = new Form1(); 

    e.Set(); 
    Application.Run(new Form1()); 
} 
Questions connexes