4 votes

Visual Basic.NET : comment créer un thread pour mettre à jour l'interface utilisateur ?

La méthode habituelle de VB pour gérer une tâche lourde en termes de calcul consiste à la confier à un thread de travail en arrière-plan, tandis que le thread principal continue à gérer l'interface utilisateur.

Supposons que, pour une raison ou une autre, je doive procéder à l'inverse : le thread principal effectue le travail de base et le thread d'arrière-plan met à jour l'interface utilisateur.

Voici ce que j'ai pour l'instant. Le seul problème est que, bien que la fenêtre de l'interface utilisateur (Form1) soit redessinée, vous ne pouvez pas interagir avec elle, ni même la déplacer ou la redimensionner (le curseur de la souris se transforme en sablier et ne clique pas).

Public Class ProgressDisplay

Private trd As Thread

    Public Sub New()
        trd = New Thread(AddressOf threadtask)
        trd.Start()
    End Sub

    Private Sub threadtask()
        Dim f1 As Form1
        f1 = New Form1
        f1.Show()
        Do
            f1.Update()
            Thread.Sleep(100)
        Loop
    End Sub

End Class

Edit : Idéalement, je dois présenter au client une interface comme celle-ci

Public Class ProgressDisplay
    Public Sub New()
    Public Sub Update(byval progress as int)
End Class

Le client l'appellera comme ceci (en fait, en c++ non géré sur COM, mais vous voyez le tableau) :

Dim prog = new ProgressDisplay()
DoLotsOfWork(addressof prog.update) ' DoLotsOfWork method takes a callback argument to keep client informed of progress

5voto

J... Points 8994

Pour reformuler clairement le problème, vous devez fournir un composant visuel à un client qui l'utilisera dans son propre programme. Le programme du client, hors de votre contrôle, bloque son thread principal (c'est-à-dire l'interface utilisateur) et vous devez faire en sorte que votre composant visuel continue de fonctionner pendant que le programme du client est gelé.

Soyons clairs ici - le fait que le programme du client soit gelé est leur et il est le résultat direct de défauts dans les domaines suivants leur code d'application. Ce qui suit est un hack, et un hack horrible que personne ne devrait jamais utiliser. Donc, pendant que vous CAN pour les raisons suivantes, il ne devrait presque jamais être considéré comme une solution recommandée pour quoi que ce soit. Elle va à l'encontre de toutes les meilleures pratiques.

Cela dit, vous devez créer un deuxième contexte d'application et une nouvelle boucle de messages qui peut continuer à fonctionner à côté du thread principal de l'interface utilisateur. Une classe comme celle-ci fonctionnerait :

Imports System.Threading

Public Class SecondUIClass

    Private appCtx As ApplicationContext
    Private formStep As Form
    Private trd As Thread
    Private pgBar As ProgressBar
    Delegate Sub dlgStepIt()

    Public Sub New()
        trd = New Thread(AddressOf NewUIThread)
        trd.SetApartmentState(ApartmentState.STA)
        trd.IsBackground = True
        trd.Start()
    End Sub

    Private Sub NewUIThread()
        formStep = New Form()
        pgBar = New ProgressBar()
        formStep.Controls.Add(pgBar)
        appCtx = New ApplicationContext(formStep)
        Application.Run(appCtx)
    End Sub

    Public Sub StepTheBar()
        formStep.Invoke(New dlgStepIt(AddressOf tStepIt))
    End Sub

    Private Sub tStepIt()
        pgBar.PerformStep()
    End Sub

End Class

Essentiellement, ce que vous faites avec la classe ci-dessus est de créer un nouveau contexte d'application dans un nouveau thread STA (en donnant à ce thread une boucle de messages). Ce contexte contient un formulaire principal (donnant au thread la propriété de celui-ci et la responsabilité du traitement de ses messages) qui peut continuer à fonctionner en dehors du thread principal de l'interface utilisateur. Cela revient à avoir un programme dans un programme - deux threads d'interface utilisateur, chacun avec son propre ensemble de contrôles mutuellement exclusifs.

Les appels qui interagissent avec l'un des contrôles appartenant au nouveau thread d'interface utilisateur (ou à son formulaire) doivent être marshallés avec la fonction Control.Invoke du fil d'exécution primaire de l'interface utilisateur (ou d'autres) pour s'assurer que votre nouveau fil d'exécution de l'interface utilisateur est celui qui effectue l'interaction. Vous pouvez également utiliser BeginInvoke ici.

Cette classe n'a AUCUN code de nettoyage, aucun contrôle de sécurité, etc. (soyez prévenus) et je ne suis même pas sûr qu'elle se terminerait de manière élégante - je vous laisse cette tâche. Ceci illustre simplement une façon de commencer. Dans le formulaire principal, vous feriez quelque chose comme :

Public Class Form1

    Private pgClass As New SecondUIClass

    Private Sub Button1_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button1.Click
        Dim i As Integer
        For i = 1 To 10
            System.Threading.Thread.Sleep(1000)
            pgClass.StepTheBar()
        Next
    End Sub
End Class

L'exécution de l'application ci-dessus créerait Form1 ainsi qu'un second formulaire créé par pgClass . En cliquant sur Button1 sur Form1 bloquerait le formulaire 1 pendant qu'il effectue sa boucle, mais le second formulaire resterait toujours vivant et réactif, mettant à jour sa barre de progression chaque fois que Form1 appelé .StepTheBar() .

La meilleure solution dans ce cas est de demander au "client" d'apprendre à programmer correctement et d'éviter de se retrouver dans cette situation. Dans le cas où cela est totalement impossible et que vous DEVEZ créer pour lui un composant qui restera vivant malgré son mauvais code, l'approche ci-dessus est probablement votre seul recours.

3voto

Steven Doggart Points 22763

L'interface utilisateur ne peut être mise à jour que par le biais du thread qui l'a créée. N'importe quel thread peut demander à invoquer une méthode sur le thread de l'interface utilisateur afin de mettre à jour l'interface utilisateur, en utilisant la fonction Control.Invoke mais cela ne fera qu'attendre que le thread de l'interface utilisateur ne soit plus occupé. Si le thread de l'interface utilisateur est occupé, l'interface utilisateur ne peut être mise à jour par rien ni personne. L'interface utilisateur n'est mise à jour que lorsque la boucle de messages principale (AKA Application.Run ) traite les messages de la fenêtre dans la file d'attente et y donne suite. Si le thread de l'interface utilisateur est occupé, bloqué dans une boucle ou en attente d'une réponse d'un serveur, il ne peut pas traiter ces messages de fenêtre (sauf si vous appelez DoEvents que je ne recommande absolument pas, dans la mesure du possible). Donc, oui, pendant que l'interface utilisateur est occupée, elle sera verrouillée. C'est la raison pour laquelle tout le monde suggère de faire toute logique commerciale importante dans un thread séparé. Si ce n'était pas un problème, pourquoi quelqu'un s'embêterait-il à faire des threads de travail ?

2voto

MicSim Points 12980

L'interface utilisateur ne peut pas être mise à jour à partir d'un autre thread que le thread principal de distribution des événements. Vous essayez donc de faire quelque chose qui ne fonctionnera pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X