Partage
  • Partager sur Facebook
  • Partager sur Twitter

[VB.NET] Multithreading / BackgroundWorker?

    11 mai 2021 à 11:56:22

    Bonjour,

    Je me tourne vers vous à nouveau car je suis actuellement dans une impasse dont je n'arrive pas à m'en sortir depuis quelques jours.

    Je m'explique, j'ai des requêtes SQL qui peuvent être plutôt longue (On parle de BDD avec plus de 100 millions de lignes). Mon objectif est d'éviter à l'utilisateur de sentir le gel avec un petit form d'attente. Avec un gif sur celui-ci. Malheureusement je ne comprends rien lors de l'ouverture du form celui-ci gel en affichant des carrés blanc. 

    J'ai évidemment fait des recherches mais je n'y trouve pas mon bonheur. J'ai bien compris que le form ne charge pas car le programme écrit les données dans un Dgv.

    Mon premier test a été d'utiliser un backgroundworker mais l'application gèle toujours. Je ne pense pas que ça soit réellement pertinent de montrer mon code car c'est du n'importe quoi mais le voici pour vous compreniez comment j'essaie de résonner si je ne suis pas très clair.

    Form1 :

    Imports System.Threading
    Public Class AlarmForm
        Private SQL As New ClassFox
     Private Sub AlarmForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            'TODO: cette ligne de code charge les données dans la table 'WWALMDBDataSet.v_AlarmHistory2'. Vous pouvez la déplacer ou la supprimer selon les besoins.
            'Me.V_AlarmHistory2TableAdapter.Fill(Me.WWALMDBDataSet.v_AlarmHistory2)
            If Not BackgroundWorker1.IsBusy Then
                BackgroundWorker1.RunWorkerAsync()
            End If
            Me.LabelNumber.Text = Me.DataGridView1.Rows.Count
            TextBoxDateDebut = Me.LabelStart.Text
            TextBoxDateFin = Me.LabelEnd.Text
        End Sub
    
        Public Sub DataGridPopulate(Optional Query As String = "")
            If Query = "" Then
                Invoke(New MethodInvoker(Sub() LabelEnd.Text = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")))
                Invoke(New MethodInvoker(Sub() TempoDateFin = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")))
                Invoke(New MethodInvoker(Sub() DataGridView1.DataSource = FormWAIT.PopulateAlarmForm()))
                Invoke(New MethodInvoker(Sub() LastDate(currentForm)))
                Exit Sub
            Else
                DataGridView1.DataSource = FormWAIT.PopulateAlarmForm(Query)
            End If
    
            DataGridView1.Columns(0).DefaultCellStyle.Format = "MM/dd/yyyy HH:mm:ss"
            Me.LabelNumber.Text = Me.DataGridView1.Rows.Count
    
        End Sub
        Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
            DataGridPopulate()
        End Sub
    End Class

    FormWAIT :

    Public Class FormWAIT
        Private SQL As New ClassFox
     Public Function PopulateAlarmForm(Optional Query As String = "")
            Me.Show()
            If Query = "" Then
                If Not BackgroundWorker1.IsBusy Then
                    BackgroundWorker1.RunWorkerAsync()
                End If
                SQL.ExexQuery("SELECT TOP 500000 .....")
                Return SQL.DBDT
                If SQL.HasException(True) Then Exit Function
    
            Else
                SQL.ExexQuery(Query)
                Return SQL.DBDT
            End If
            If SQL.HasException(True) Then Exit Function
            '  Me.Close()
        End Function
    
     Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
            Dim sum As Integer = 0
            For i As Integer = 1 To 100
                Thread.Sleep(100)
                sum = sum + i
                BackgroundWorker1.ReportProgress(i)
    
                If BackgroundWorker1.CancellationPending Then
                    e.Cancel = True
                    BackgroundWorker1.ReportProgress(0)
                    Return
                End If
            Next
            e.Result = sum
        End Sub
    
        Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
            ProgressBar1.Value = e.ProgressPercentage
            Label1.Text = e.ProgressPercentage.ToString() + "%"
        End Sub
    
        Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
            If e.Cancelled Then
                Label1.Text = "test"
            ElseIf e.Error IsNot Nothing Then
                Label1.Text = e.Error.Message
            End If
            Label1.Text = e.Result.ToString
        End Sub
    
        Private Sub ButtonCancel_Click(sender As Object, e As EventArgs) Handles ButtonCancel.Click
            If BackgroundWorker1.IsBusy Then
                BackgroundWorker1.CancelAsync()
            End If
        End Sub
    End Class


    Je vous remercie par avance de votre aide !

    -
    Edité par Doristos 11 mai 2021 à 11:57:23

    • Partager sur Facebook
    • Partager sur Twitter
      11 mai 2021 à 18:58:49

      il faudrait que ce soit l'inverse, que ta requête soit lancée dans une tache en asynchrone
      • Partager sur Facebook
      • Partager sur Twitter
        12 mai 2021 à 10:17:46

        Salut umfred,

        merci pour ta réponse. Mais j'avais déjà essayé cette solution sans succès. Je vais m'y repencher et partager ce que j'ai faits pour voir où est mon erreur.

        Encore merci et bonne journée !

        EDIT : J'ai des problème d'index est-il possible d'attendre que le bgw est terminé pour continuer l'exécution du thread principal ? ( Sans utiliser de Thread Sleep)

        -
        Edité par Doristos 12 mai 2021 à 11:42:11

        • Partager sur Facebook
        • Partager sur Twitter
          12 mai 2021 à 14:41:41

          Pourquoi ne pas laisser la DataGridView et tous les autres contrôles afficher une valeur par défaut/d'attente en attendant la fin du "bgw" ?
          • Partager sur Facebook
          • Partager sur Twitter
          Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
            12 mai 2021 à 16:34:41

            Salut bacelar,

            Tout d'abord merci, et effectivement je n'ai plus aucun problème d'index mais après avoir donné une valeur par défaut pour le DataGridView (Rows.add) plus aucune donnée n'est écrite sur le DGV. J'ai essayé ça mais j'ai toujours le même problème :

                Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
                    Form1.DataGridView1.Columns(0).DefaultCellStyle.Format = "MM/dd/yyyy HH:mm:ss"
                    Form1.LabelNumber.Text = AlarmForm.DataGridView1.Rows.Count
            LastDate(currentForm) AlarmForm.DataGridView1.DataSource = SQL.DBDT Me.Close() End Sub



            -
            Edité par Doristos 12 mai 2021 à 16:36:06

            • Partager sur Facebook
            • Partager sur Twitter
              12 mai 2021 à 16:51:20

              Pourquoi ne pas laisser la DataGrid dans aucune "Row" ?

              Ligne 5 : Vous fermez votre formulaire ???

              Attention au passage d'un thread à l'autre. Vous êtes sûr de ne pas avoir besoin de passer par un "Invoke" quand du code dans un "BackgroundWorker1_RunWorkerCompleted" mettent à jour des contrôles ??? (C'est fonction des réglages de synchronisation)

              • Partager sur Facebook
              • Partager sur Twitter
              Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                12 mai 2021 à 16:58:08

                il me semble qu'en mettant la DataSource à vbnull avant de remettre la bonne source puis un refresh() de la datagridview, ça fait le job.
                • Partager sur Facebook
                • Partager sur Twitter
                  17 mai 2021 à 14:22:31

                  Bonjour Bacelar, je ne comprends pas votre première question. Je suis bien obligé de lui donner une valeur par défaut pour ne pas avoir de problème d'index non ? 

                  Oui mais c'était juste essayer deux trois choses que j'ai déjà retirées. 

                  Je vais tout de suite essayer avec invoke et j'éditerais mon message si ça fonctionne. 

                  EDIT : Malheureusement le problème persiste :( 

                  Bonjour Umfred,

                  Alors j'ai essayé cette méthode mais j'ai toujours le même problème, le datagridview ne se met pas à jour malgré le refresh(). 

                  Je vous montre ce que j'ai faits :  

                      Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
                          AlarmForm.DataGridView1.DataSource = vbNull
                          AlarmForm.DataGridView1.DataSource = SQL.DBDT
                          AlarmForm.DataGridView1.Refresh()
                          AlarmForm.DataGridView1.Columns(0).DefaultCellStyle.Format = "MM/dd/yyyy HH:mm:ss"
                          AlarmForm.LabelNumber.Text = AlarmForm.DataGridView1.Rows.Count
                          LastDate(currentForm)
                      End Sub


                  Merci encore pour votre temps.

                  EDIT 2 : 

                  J'ai affiché le dgv avec toutes les données à l'aide du thread principal, et avec le backgrounworker j'ai essayé de mettre la datasource à vbnull. 

                  Et les données restes écrites, je n'ai donc pas accès au datasource avec mon bgw. Malgré le invoke ça ne fonctionne toujours pas et le DataGrid est bien en "Public". Je ne vois vraiment pas comment modifier la datasource.  

                  -
                  Edité par Doristos 17 mai 2021 à 16:09:36

                  • Partager sur Facebook
                  • Partager sur Twitter
                    17 mai 2021 à 16:48:55

                    Je suis complètement perdu dans votre code.

                    Pourquoi faites vous des choses aussi complexes comme des backgroundWorker dans 2 formulaires différents, etc...???

                    Plus c'est simple, mieux c'est.

                    L'utilisation du débogueur peut donner des indications sur comment tout cela fonctionne. Moi, je suis un peu perdu avec tout ces "async" qui me semblent inutiles. 

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                      17 mai 2021 à 16:58:58

                      Effectivement c'est un peu le bazar voici le code actuel avec un seul backgroundworker pour le formWAIT:

                      AlarmForm :

                        Public Sub DataGridPopulate(Optional Query As String = "")
                                  LabelEnd.Text = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")
                                  TempoDateFin = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")
                                  FormWAIT.PopulateAlarmForm()
                          End Sub

                      FormWAIT :

                      Public Class FormWAIT
                          Private SQL As New ClassFox
                          Private Sub FormWAIT_Load(sender As Object, e As EventArgs) Handles MyBase.Load
                          End Sub
                          Public Function PopulateAlarmForm(Optional Query As String = "")
                                  If Not BackgroundWorker1.IsBusy Then
                                      BackgroundWorker1.RunWorkerAsync()
                                  End If
                                  If SQL.HasException(True) Then Exit Function          
                          End Function
                      
                          Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
                              SQL.ExexQuery("SELECT TOP 500000 ...................")
                      
                          End Sub
                      
                          Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
                              AlarmForm.DataGridView1.DataSource = vbNull
                              AlarmForm.DataGridView1.DataSource = SQL.DBDT
                              AlarmForm.DataGridView1.Refresh()
                              AlarmForm.DataGridView1.Columns(0).DefaultCellStyle.Format = "MM/dd/yyyy HH:mm:ss"
                              AlarmForm.LabelNumber.Text = AlarmForm.DataGridView1.Rows.Count
                              'lastDate(currentForm)
                          End Sub
                      
                          Private Sub ButtonCancel_Click(sender As Object, e As EventArgs) Handles ButtonCancel.Click
                              If BackgroundWorker1.IsBusy Then
                                  BackgroundWorker1.CancelAsync()
                              End If
                          End Sub




                      -
                      Edité par Doristos 17 mai 2021 à 17:07:40

                      • Partager sur Facebook
                      • Partager sur Twitter
                        17 mai 2021 à 17:24:30

                        A quoi sert Query car pas utiliser dans une requête (a priori) du backgroundworker ? Edit:OK, vu après ton edit ...

                        Pourquoi lancer le bgw si la Query est vide ? Edit: idem

                        Ta fonction PopulateAlarmForm ne renvoie rien, donc pourquoi utiliser son retour pour initialiser la datasource ? Edit: idem
                        Le bouton Cancel ne sert à rien ici, tu ne t'occupes pas de vérifier la demande d'annulation du bgw

                        -
                        Edité par umfred 17 mai 2021 à 17:26:27

                        • Partager sur Facebook
                        • Partager sur Twitter
                          17 mai 2021 à 19:19:58

                          J'ai édité mon code pour que ça soit plus épuré mais Query est une variable dont j'ajouterais des requêtes préparées plus tard. (J'essaie dans un premier temps de juste exécuté celle-ci)

                          Effectivement j'avais supprimé la vérification d'annulation en pensant que j'avais faits une erreur là dessus. 

                          Mais je ne comprends toujours pas pourquoi le dgv n'affiche pas les données pourtant avec le débogueur je lance bien la requête et j'arrive bien au  

                          AlarmForm.DataGridView1.DataSource = SQL.DBDT



                          Et quand j'exécute tout dans le "PopulateAlarmForm" le dgv se met bien à jour. Je suppose que bacelar doit avoir raison avec les invoke mais je n'y arrive définitivement pas.

                          -
                          Edité par Doristos 17 mai 2021 à 19:20:29

                          • Partager sur Facebook
                          • Partager sur Twitter
                            17 mai 2021 à 19:28:31

                            >bacelar doit avoir raison avec les invoke mais je n'y arrive définitivement pas

                            Code source et messages d'erreurs, SVP.

                            • Partager sur Facebook
                            • Partager sur Twitter
                            Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                              18 mai 2021 à 10:05:26

                              Bonjour,

                              ça m'aurait bien aidé un message d'erreur mais je n'ai aucune erreur omis le dgv qui ne se met pas à jour. 

                              EDIT : D'après le débogueur 

                              Invoke(New MethodInvoker(Sub() AlarmForm.DataGridView1.DataSource = SQL.DBDT))

                              Le SQL.DBDT = Nothing 

                              AlarmForm :

                              Public Class AlarmForm
                                  Private SQL As New ClassBDD
                              Private Sub AlarmForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
                                      'TODO: cette ligne de code charge les données dans la table 'WWALMDBDataSet.v_AlarmHistory2'. Vous pouvez la déplacer ou la supprimer selon les besoins.
                                      'Me.V_AlarmHistory2TableAdapter.Fill(Me.WWALMDBDataSet.v_AlarmHistory2)
                                      DataGridPopulate()
                                  End Sub
                                  Public Sub DataGridPopulate(Optional Query As String = "")
                                      If Query = "" Then
                                          LabelEnd.Text = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")
                                          TempoDateFin = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")
                                          DataGridView1.Rows.Add("02/12/9999 00:00:00")
                                          FormWAIT.PopulateAlarmForm()
                                          'DataGridView1.DataSource = FormWAIT.PopulateAlarmForm()
                                          Exit Sub
                                      Else
                              FormWAIT.PopulateAlarmForm(Query)            
                                      End If



                              ClassBDD :

                              Imports System.Data.SqlClient
                              Public Class ClassBDD
                                  Public DBCon As New SqlConnection(ConnString)
                                  Private DBCmd As SqlCommand
                                  'DB DATA
                                  Public DBDA As SqlDataAdapter
                                  Public DBDT As DataTable
                                  'Query PARAMETERS 
                                  Public Params As New List(Of SqlParameter)
                                  'Query Statistics
                                  Public RecordCount As Integer
                                  Public Exception As String
                                  Public Sub New()
                                  End Sub
                                  'Allow Connection String override
                                  Public Sub New(ConnectionString As String)
                                      DBCon = New SqlConnection(ConnectionString)
                                  End Sub
                              
                                  'Execute Query Sub
                                  Public Sub ExexQuery(Query As String)
                                      'Reset QueyStat
                                      RecordCount = 0
                                      Exception = ""
                                      Try
                                          DBCon.Open()
                                          'Create DB Command
                                          DBCmd = New SqlCommand(Query, DBCon)
                                          'Load Params INTO db command
                                          Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))
                                          Params.Clear()
                                          'Execute command
                                          DBDT = New DataTable
                                          DBDA = New SqlDataAdapter(DBCmd)
                                          RecordCount = DBDA.Fill(DBDT)
                              
                                      Catch ex As Exception
                                          Exception = "ExexQuery Error : " & vbNewLine & ex.Message
                                      Finally
                                          'Close
                                          If DBCon.State = ConnectionState.Open Then DBCon.Close()
                              
                                      End Try
                                  End Sub
                                  Public Sub TestConn()
                                      Try
                                          DBCon.Open()
                                          MsgBox("OPEN", MsgBoxStyle.Information, "SUCCESFULL")
                                      Catch
                                          MsgBox("ERROR", MsgBoxStyle.Critical, "FAILED")
                                      End Try
                                      If DBCon.State = ConnectionState.Open Then DBCon.Close()
                                  End Sub
                              
                                  'ADD Params
                                  Public Sub AddParam(Name As String, Value As Object)
                                      Dim NewParam As New SqlParameter(Name, Value)
                                      Params.Add(NewParam)
                                  End Sub
                              
                              
                                  Public Function HasException(Optional Report As Boolean = False) As Boolean
                                      If String.IsNullOrEmpty(Exception) Then Return False
                                      If Report = True Then MsgBox(Exception, MsgBoxStyle.Critical, "Execption:")
                                      Return True
                              
                                  End Function
                              End Class
                              


                              formWAIT : 

                              Public Class FormWAIT
                                  Private SQL As New ClassBDD
                                  Private Sub FormWAIT_Load(sender As Object, e As EventArgs) Handles MyBase.Load
                                  End Sub
                                  Public Function PopulateAlarmForm(Optional Query As String = "")
                                      Me.Show()
                                      If Query = "" Then
                                          If Not BackgroundWorker1.IsBusy Then
                                              BackgroundWorker1.RunWorkerAsync()
                                          End If
                                          If SQL.HasException(True) Then Exit Function
                              
                                      Else
                                          If Not BackgroundWorker1.IsBusy Then
                                              BackgroundWorker1.RunWorkerAsync()
                                          End If
                              
                                      End If
                                      If SQL.HasException(True) Then Exit Function
                                  End Function
                              
                                  Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
                                      SQL.ExexQuery("SELECT TOP 50000 ..............")
                              
                                  End Sub
                              
                                  Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
                                      Invoke(New MethodInvoker(Sub() AlarmForm.DataGridView1.DataSource = vbNull))
                                      Invoke(New MethodInvoker(Sub() AlarmForm.DataGridView1.DataSource = SQL.DBDT))
                                      Invoke(New MethodInvoker(Sub() AlarmForm.DataGridView1.Refresh()))
                                      Invoke(New MethodInvoker(Sub() AlarmForm.DataGridView1.Columns(0).DefaultCellStyle.Format = "MM/dd/yyyy HH:mm:ss"))
                                      Invoke(New MethodInvoker(Sub() AlarmForm.LabelNumber.Text = AlarmForm.DataGridView1.Rows.Count))
                                      'lastDate(currentForm)
                                      'Me.Close()
                                  End Sub




                              -
                              Edité par Doristos 18 mai 2021 à 10:29:58

                              • Partager sur Facebook
                              • Partager sur Twitter
                                18 mai 2021 à 14:19:52

                                >Le SQL.DBDT = Nothing

                                Ce qui explique tout le reste.

                                Où ce SQL.DBDT est sensé être rempli ?

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                                  18 mai 2021 à 14:55:08

                                  Bonjour, 

                                  Ligne 35 de la classBDD

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    21 mai 2021 à 12:26:23

                                    Bon, on va commencer par dégager ces horribles "Try/Catch/Finally" et utiliser l'instruction "Using/End Using" à la place de ces machins qui planquent les erreurs sous le tapis.

                                    OK ?

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.

                                    [VB.NET] Multithreading / BackgroundWorker?

                                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                    • Editeur
                                    • Markdown