11
votes

Pourquoi le modèle permet-il de mettre à jour l'interface utilisateur d'un fil différent non intégré à la framework .NET?

Je sais "Pourquoi mon cadre est-il comme / pas comme XYZ?" Les questions sont un peu dangereuses mais je veux voir ce qui me manque.

dans Winforms, vous ne pouvez pas mettre à jour l'interface utilisateur d'un autre fil. La plupart des gens utilisent Ce modèle : P>

public static TResult SafeInvoke(this T isi, Func call) where T : ISynchronizeInvoke
{
    if (isi.InvokeRequired) { 
        IAsyncResult result = isi.BeginInvoke(call, new object[] { isi }); 
        object endResult = isi.EndInvoke(result); return (TResult)endResult; 
    }
    else
        return call(isi);
}

public static void SafeInvoke(this T isi, Action call) where T : ISynchronizeInvoke
{
    if (isi.InvokeRequired)
        isi.BeginInvoke(call, new object[] { isi });
    else
        call(isi);
}


2 commentaires

Quelque chose d'un peu lié à votre question est ici, pas comme une réponse, mais juste pour vous de lire: Stackoverflow.com/questions/3972727/...


Voir aussi Stackoverflow.com/questions/3768954/...


5 Réponses :


4
votes

Je pense que le problème est que cette construction aurait probablement besoin d'être intégrée à tous les biens de tous les composants de l'interface utilisateur dans le cadre. Cela devra également être fait par des développeurs tiers en faisant de telles composantes.

Une autre option peut être que les compilateurs ont ajouté la construction autour d'un accès aux composants de l'interface utilisateur, mais cela ajouterait la complexité aux compilateurs à la place. Si je comprends bien, pour une fonctionnalité de faire place dans le compilateur, il devrait

  • Eh bien, toutes les autres fonctionnalités existantes dans le compilateur
  • avoir des coûts de mise en œuvre qui en valent la peine de la peine par rapport au problème qu'il résout

    Dans ce cas particulier, le compilateur devrait également avoir un moyen de déterminer si un type dans le code est un type qui nécessite la construction de la synchronisation autour de celle-ci ou non.

    Bien sûr, toutes ces spéculations sont des spéculations, mais je pouvais imaginer que ce type de raisonnement réside derrière la décision.


1 commentaires

Très bonne réponse. Un détail: en utilisant les méthodes d'extension (à partir de l'exemple de 2e code), il n'aurait pas besoin d'être intégré à chaque propriété.



4
votes

It est intégré, la classe de l'entreprise d'arrière-plan l'implémente automatiquement. Ses manutentionnaires d'événements fonctionnent sur le fil de l'interface utilisateur, en supposant qu'il soit correctement créé.

Prendre une approche plus cynique de ceci: c'est un anti-motif. Au moins dans mon code, il est extrêmement rare que la méthode même fonctionne à la fois sur le fil de l'interface utilisateur et du fil de travail. Il n'a pas de sens de tester invoquée, je sais que c'est toujours vrai depuis que j'ai écrit le code qui l'appellerait intentionnellement comme code qui fonctionne dans un fil séparé.

Avec toute la plomberie nécessaire nécessaire pour rendre ce code sans danger et interoper correctement. Utilisation de l'instruction de verrouillage et du manuel / des rétives automatiques pour signaler entre les threads. Et si invoquée serait faux , je sais que j'ai un bogue dans ce code. Parce que vous invoquer au fil de l'interface utilisateur lorsque les composants de l'interface utilisateur ne sont pas encore créés ou disposés est très mauvais. Il appartient à un appel debug.Assert () au mieux.


4 commentaires

Je vois que votre point de vue est-il éventuellement un anti-motif. Un détail sur la classe Travailleur de fond: La mise à jour de l'interface utilisateur croisée n'est pas intégrée à Dowork . Pour obtenir leur optimisation, vous devez uniquement mettre à jour l'interface utilisateur de ProgressChanged (ou terminé ). Je me rends compte que c'est une préférence personnelle, mais je trouve plus facile d'écrire le code de la chaudron et de ne pas avoir à sauter à travers les cerceaux de mise à jour de l'interface utilisateur via progressanchanged .


Notez que invoquionnaire est faux jusqu'à ce qu'un contrôle soit ajouté à un formulaire et ce formulaire a été montré. Donc, si vous par exemple. Contrôles de test dans les tests d'unité sans les créer réellement ou si vous créez un formulaire au début et l'afficher plus tard, vous avez besoin exactement de ce modèle.


Un autre point: Si vous vous abonnez à un événement d'un autre objet, il peut être judicieux de tester les invoqués dans le gestionnaire d'événements. Prenez FileSystemWatcher par exemple - je ne trouve rien dans la documentation que les garanties que les événements de fil seront soulevées à partir. C'est un détail de mise en œuvre. IMO, vous ne devriez pas compter sur un détail de mise en œuvre d'une autre classe. Vous devez donc utiliser le invoquité requis -Pattern.


@nikie - Ce n'est pas la façon dont ça fonctionne. FSW pour une augmentation des événements sur un thread TP, sauf si vous attribuez la propriété SynchronizingObject. Bien documenté. Il faut être, le filetage n'est jamais un détail qui peut être laissé sans papiers. Si pour une raison quelconque, il est sans papiers, alors oui, par tous les moyens, utilisez invoquée.



2
votes

1 commentaires

PDC monte. Je me demande s'ils annonceront quelque chose de pertinent à cette réponse. J'ai vu quelques personnes spéculatives sur l'Eric Lippert's Blog ( blogs.msdn.com/b/ericlippert/archive/2010/10/21/... ) que c # 5 pourrait exister et avoir des fonctionnalités relatives à cela, mais bien sûr ERIC ne peut "ni confirmer ni nier l'existence d'un produit hypothétique" C # 5 "inutilisé ou de tout fonctionnalité hypothétiquement pourrait ou non implémenter."



0
votes

Si je comprends votre question correctement, vous voudriez que le cadre (ou le compilateur ou un autre morceau de technologie) inclure Invoke / Begininvoke / Endinvoke autour de tous les membres publics d'un objet UI, afin de le faire filer en toute sécurité. Le problème est que seul ne ferait que votre fil de code sûr. Vous devez toujours utiliser Très-unvoke et d'autres mécanismes de syncronisation très souvent. (Voir ceci Grand article sur la sécurité du fil sur Eric Lippert ''s Blog )

Imaginez que vous écrivez le code comme xxx

si le cadre ou le compilateur Enveloppé tous les accès à SELECTELITEM et l'appel sur Supprimer dans un appel de beginInvoke / Invoquez, ce ne serait pas soyez en sécurité. Il y a une condition de course potentielle si SELECTELITEMEM est non nulle lorsque la clause IF est évaluée, mais un autre thread la définit sur NULL avant la fin du blocage de l'époque. Probablement la totalité de la clause if-then-ele-else devrait être enveloppée dans un appel de BeginInvoke, mais comment le compilateur devrait-il le savoir?

Maintenant, vous pourriez dire "mais c'est vrai pour tous les objets mutables partagés, je ' ll juste ajouter des serrures ". Mais c'est assez dangereux. Imaginez que vous ayez fait quelque chose comme: xxx

Ceci va une impasse: la méthode A est appelée sur un fil d'arrière-plan. Il acquiert la serrure, les appels invoquent. Invoke attend une réponse de la file d'attente du thread de l'UI. Dans le même temps, la méthode B est exécutée dans le thread principal (par exemple dans une touche.Cliquez-le-gestionnaire). L'autre thread contient myListboxLock , il ne peut donc pas entrer dans la serrure - maintenant les deux threads attendent les uns sur les autres, ni ne peuvent faire de progrès.

Recherche et évitant les bugs de filetage comme Celles-ci sont assez difficiles que cela, mais au moins maintenant, vous pouvez voir que vous appelez appelez et que c'est un appel de blocage. Si des biens pouvaient bloquer silencieusement, les insectes comme ceux-ci seraient extrémités plus difficiles à trouver.

moral: le filetage est difficile. Le fil-ui-interaction est encore plus difficile, car il n'y a qu'une seule file d'attente partagée unique. Et malheureusement, ni nos compilateurs ni nos cadres sont suffisamment intelligents pour "faire fonctionner juste le bien".


0 commentaires

15
votes

Je pensais qu'il serait peut-être intéressant de mentionner pourquoi il s'agit d'un fil d'interface utilisateur en premier lieu. Il s'agit de réduire le coût de la production de composants d'interface utilisateur tout en augmentant leur exactitude et leur robustesse.

Le problème de base de la sécurité du thread est qu'une mise à jour non atomique de l'état privé peut être observée à mi-fini sur un thread de lecture si le fil d'écriture est à mi-chemin lorsque la lecture se produit.

Pour atteindre la sécurité du fil, vous pouvez faire un certain nombre de choses que vous pouvez faire.

1) verrouille explicitement toutes les lectures et écrit. Avantages: maximalement flexible; Tout fonctionne sur n'importe quel fil. Inconvénients: maximalement douloureux; Tout doit être verrouillé tout le temps. Les serrures peuvent être soumis, ce qui les rend lents. Il est très facile d'écrire des blocages. Il est très facile d'écrire du code qui gère la rentrinie malade. Et ainsi de suite.

2) Autoriser les lectures et écrit uniquement sur le thread qui a créé l'objet. Vous pouvez avoir plusieurs objets sur plusieurs threads, mais une fois qu'un objet est utilisé sur un thread, c'est le seul fil qui peut l'utiliser. Par conséquent, il n'y aura pas de lecture et écrit simultanément sur différents threads, de sorte que vous n'avez pas besoin de verrouiller quoi que ce soit. Il s'agit du modèle "appartement", et c'est le modèle que la grande majorité des composants de l'interface utilisateur s'attend à attendre. Le seul état qui doit être verrouillé est l'état partagé par plusieurs instances sur différents threads, et c'est assez facile à faire.

3) Autorise des lectures et écrit uniquement sur le fil de possession, mais permettez à un fil de donner explicitement la propriété à un autre lorsqu'il n'y a pas de lecture et écrit en cours. Il s'agit du modèle "location" et c'est le modèle utilisé par les pages de serveurs actifs pour recycler les moteurs de script.

Étant donné que la grande majorité des composants de l'interface utilisateur sont écrits pour travailler dans le modèle d'appartement, et il est douloureux et difficile de faire tous ces composants à filetage libre, vous êtes coincé avec qui devoir faire tout le travail de l'interface utilisateur sur le fil de l'interface utilisateur.


0 commentaires