2009-02-09 1 views
6

Quelqu'un peut-il s'il vous plaît expliquer comment cela pourrait éventuellement se produire? Je suis complètement conscient de la programmation avec la sécurité des threads à l'esprit et comme vous pouvez le voir, je suis en charge des mises à jour de l'interface utilisateur via un formulaire InvokeRequired check ici, tout a bien fonctionné et aucun changement pour rompre ce que je suis conscient maintenant tout à coup comme je la programmation d'autres parties de l'application (? peut-être ajouté à cette méthode à une étape que je ne me souviens pas) Je intimitently, ce type d'erreur:Impossible (comment je déteste utiliser ce mot) erreur de threading croisée?

alt text

tout d'abord, si InvokeRequired = true, cela signifie que BeginInvoke() est exécuté, la méthode est [en file d'attente] pour être appelée de nouveau et InvokeRequired doit être égal à false?

Je ne devrais pas recevoir cette exception car elle devrait être prise en compte?

l'espoir d'entendre certains gourou de multi threading :)

Graham

+0

Mise à jour: Je l'ai fait un peu plus de tests, juste à un long shot et mettre le code de l'interface utilisateur dans un autre et supprimé le " retourner "déclaration et il n'a pas encore échoué. Est-ce que cela signifie, Invoke n'était pas nécessaire, mais nous ne pouvons toujours pas effectuer cela, ce que nous pensons être le fil de l'interface utilisateur? Soupir. – GONeale

Répondre

9

Je suppose que InvokedRevoked peut vous mentir. Un contrôle WinForm diffère la création de HWND Win32 sous-jacent du contrôle jusqu'à ce qu'un appel de méthode en ait réellement besoin. InvokeRequired renverra false si le HWND n'a pas encore été créé.

Pour une explication détaillée, voir: "Mysterious Hang or The Great Deception of InvokeRequired"

Si vos requêtes de fil de fond InvokeRequired avant le thread d'interface utilisateur a provoqué le contrôle pour créer paresseusement HWND, InvokeRequired sera (à tort) dire à votre fil d'arrière-plan qu'il fait pas besoin d'utiliser Invoke() pour renvoyer le contrôle au thread d'interface utilisateur. Lorsque le fil d'arrière-plan accède alors au contrôle, BAM! "InvalidOperationException: l'opération inter-thread n'est pas valide!"

Le thread d'interface utilisateur peut forcer manuellement le contrôle à créer sa poignée HWND pour ainsi contrôler.InvokeRequired connaîtra le thread d'interface utilisateur est le propriétaire de contrôle:

Control control = new Control(); 
IntPtr handle = control.Handle; // if you ask for HWND, it will be created 
+0

Merci cpeterso, je comprends fondamentalement. Ma dernière pensée est de placer la logique dans une autre déclaration car je ne reçois pas mon erreur quand je le fais. Laissez-moi vous expliquer .. – GONeale

+0

Je comprends que le code ne s'exécutera pas après l'instruction "return". Ce n'est pas mon argument, comme ce qui a été écrit ci-dessus. Je crois que cela fonctionne avec l'instruction else en raison de ce que vous dites InvokeRequired mentant à moi .. – GONeale

+0

en ajoutant toute la logique dans l'autre, seulement vraiment quand Invoke n'est pas nécessaire le traitement de l'interface utilisateur sera activé. De ce point de vue, je crois que c'est pourquoi il fonctionne, pas tellement "retour" vs "autre" argument. Quelles sont vos pensées? – GONeale

-1

Utilisez

if (InvokeRequired) 
{ 
    //invoke 
} 
else 
{ 
    //do 
} 
+0

Il a ça. If (InvokeRequired) {Invoquer(); revenir; } est le même que votre code. –

+0

Merci bleevo et Jon, comme mon premier commentaire indiqué, j'ai essayé ceci, par désespoir, et je ne reçois pas l'erreur dans cette méthode maintenant, mais une autre méthode de mise à jour de l'interface utilisateur, donc je ne peux pas dire si supprimer le retour réparer, ou c'est juste encore l'intermittence. – GONeale

+0

En fait ce n'est pas le cas. Il n'a pas d'autre instruction, juste une autre if-statement. Donc, étant donné que InvokeRequired n'est pas fiable à 100%, il est confronté à des violations de threads croisés en accédant à son panneau à partir du thread. –

5

La plupart des gens voient cette erreur et voir une chose, « vous n'accédez pas à ce contrôle de la fil d'interface utilisateur principal. " En réalité, vous pouvez avoir 100 threads d'interface utilisateur si vous le souhaitez (le comportement pour cela n'est pas défini, mais pris en charge). Les chances sont que panelMain soit créé sur un thread différent de (this); Je ne peux pas voir le code - mais il semble que vous le créez dans votre worker/thread.

Pour confirmer le comportement, essayez ceci:

Action addAction = new Action (
    new delegate { panelMain.Controls.Add(UCLoadingScreen); }) 
if(panelMain.InvokeRequired) 
{ 
    panelMain.Invoke(addAction); // Breakpoint here. 
} 
else 
{ 
    addAction(); 
} 

Préparez-vous à une autre erreur (un contrôle enfant sur un thread différent de son parent, ne sais pas quelle erreur vous obtiendrez, mais je suis certain que vous volonté). Ce n'est pas une solution.

Une usine corrigera ceci cependant:

public void CreateControl<T>() where T : new() 
{ 
    if(InvokeRequired) 
    { 
     return (T)Invoke(new Func<T>(CreateControl<T>())); 
    } 
    return new T(); 
} 

EDIT: panelMain pourrait ne pas être le fil « transgresseur », comme je l'ai dit, les contrôles parental de différents threads est des résultats dans le comportement très défini. Assurez-vous que tous vos contrôles sont créés dans le contexte du fil de votre formulaire principal.

+0

Salut Jon, je viens de tester cela, et il intervient dans (panelMain.InvokeRequired) FYI, j'allais aussi vous mentionner que panelMain a été créé dans le concepteur Win Form et n'aurait pas été créé par un thread de travail. Mais comme vous l'avez compris, panelMain peut ne pas être le contrevenant. – GONeale

+0

Jon, en raison de limitations de messagerie ici, j'ai écrit ma dernière tentative dans les commentaires de cepterso. S'il vous plaît voir et évaluer. – GONeale

0

Il n'y a pas une telle chose comme une erreur de filetage transversal impossible!

+0

lol d'où mon commentaire entre parenthèses! mais j'avais besoin d'écrire quelque chose pour attirer votre attention :) – GONeale

0

J'espère que cela fonctionne pour vous. Je suppose que vos trucs de gui sont sur un fil. Il suffit d'initialiser ce singleton et de vous y fier chaque fois que vous souhaitez appeler la propriété Control.InvokeRequired.

Cheers,

-Greg

public sealed class UiThread 
{ 
    #region Singleton 
    // Singleton pattern implementation taken from Jon Skeet's C# and .NET article www.yoda.arachsys.com/csharp/singleton.html 

    UiThread() { } 

    public static UiThread Instance { get { return Nested.instance; } } 

    class Nested 
    { 
    // Explicit static constructor to tell C# compiler 
    // not to mark type as beforefieldinit 
    static Nested() { } 

    internal static readonly UiThread instance = new UiThread(); 
    } 
    #endregion 

    int _uiThreadId = 0; 

    public void SetUiThread(Thread thread) 
    { 
    if (_uiThreadId != 0) 
     throw new ApplicationException("UI Thread has already been established!"); 

    if (thread.ManagedThreadId == 0) 
     throw new ArgumentException("Unexpected thread id value of 0!"); 

    if (thread.IsBackground) 
     throw new ArgumentException("Supplied thread should not be a background thread!"); 

    if (thread.IsThreadPoolThread) 
     throw new ArgumentException("Supplied thread should not be a thread pool thread!"); 

    _uiThreadId = thread.ManagedThreadId; 
    } 

    /// <summary> 
    /// It's possible for InvokeRequired to return false when running in background thread. 
    /// This happens when unmanaged control handle has not yet been created. 
    /// We second-guess Microsoft's implementation in this case, checking against foreground thread's Id. 
    /// </summary> 
    /// <param name="control">Control to check against.</param> 
    public bool InvokeRequired(Control control) 
    { 
    if (control.InvokeRequired) 
     return true; 

    IntPtr unmanagedHandle = control.Handle; 
    bool newResult = control.InvokeRequired; 

    if (unmanagedHandle == IntPtr.Zero) 
    { 
     // Trace.WriteLine() call here forces unmanagedHandle's initialization, 
     // even with optimizing compiler. 
     Trace.WriteLine(string.Format("Control handle could not be established! Control was {0}.", control.ToString())); 
    } 

    bool retVal = InvokeRequired(); 

    // Break whenever the result of our check does not match theirs. 
    Debug.Assert(retVal == newResult); 

    // Return our InvokeRequired result over theirs 
    // to keep with the tradition of updating controls from foreground only. 
    return retVal; 
    } 

    /// <summary> 
    /// Prefer to use overload with Control argument if at all possible. 
    /// It's possible for InvokeRequired to return false when running in background thread. 
    /// This happens when unmanaged control handle has not yet been created. 
    /// We second-guess Microsoft's implementation in this case, checking against foreground thread's Id. 
    /// </summary> 
    public bool InvokeRequired() 
    { 
    if (_uiThreadId == 0) 
     throw new ApplicationException("UI Thread has not been established!"); 

    return (Thread.CurrentThread.ManagedThreadId != _uiThreadId); 
    } 

}

Questions connexes