none
Comment gérer les Tasks RRS feed

  • Question

  • Bonjour

    Je ne suis pas un expert mais ai été amené à m’intéresser aux Tasks car le programme que je développe lit et écrit des gros fichiers texte. Ceci amène le programme à ne plus réagir aux click des boutons, aux menus et à la progression de  la ProgressBar.

    Je crois avoir compris qu'il me fallait introduire des procédures asynchrone pour résoudre ce problème.

    Je confirme que je n'ai pas besoin d'effectuer des tâches en parallèle et que celles-ci doivent s'effectuer dans l'ordre de programmation car le résultat de l'une est utilisée par la suivante.

    Mes données sont basées sur une classe nommée Toponyme dotée de plusieurs propriétés telles que Nom, Code Postal etc..

    L'ensemble de ces toponymes sont regroupés et triés dans une SortedList(Of) avec 

    J'ai le code suivant 

    Dim Communes As SortedList(Of String, Toponyme) 

    Dim tLireFichierSource = Task(Of SortedList(Of String, Toponyme)).     

          Factory.StartNew(Function()   

           CommunesSource = LireFichierSource()     

           Return CommunesSource 

           End Function) 

                          End Function)
    While tLireFichierSource.Status <> TaskStatus.RanToCompletion

    End While

    If tLireFichierSource.Status = TaskStatus.RanToCompletion Then   

            If CommunesSource.Count = 0 Or CommunesSource Is Nothing Then Exit Sub 

    End If

    Plusieurs questions

    1)La procédure LireFichierSource est définie comme synchrone (sans Async) et les fonction de lecture son également synchrone.

    Il y a t il une différence de rapidité de traitement si je la transforme en Asynchrone ?

    Ou le fait quelle soit appelée dans la tâche tLireFichierSource la rend automatiquement asynchrone?

    Cette procédure a été définie comme une fonction pour récupérer son résultat CommunesSource mais si je la défini comme Async la Function n'est pas acceptée.

    Comment faire si je dois la rendre asynchrone et lui demander de renvoyer un résultat

    2) Comme j'ai besoin d'attendre la fin de l’exécution de la première tâche pour passer à la suivante j'ai fait un test sur le Statut de celle-ci et ai mis une boucle sans fin pour attendre quelle soit terminée! Je suis persuadé que ce n'est surement pas la bonne methode.

    J'ai donc essayé avec:

     

    Dim tLireFichierGed As Task = tLireFichierSource.ContinueWith(Sub(antecedent)     

                      LireFichierGed()                                       

                        End Sub)

    Où LireFichierGed est une deuxième procédure de lecture de fichier.

    Mais ContinueWith ne semble pas marcher car le programme n'attend pas la fin de la première procédure.

    3) Comment mettre à jour une progress bar pour montrer l'avancement d'une procé"dure dans ce contexte.

    Merci pour votre aide

    Bernard


    lundi 21 décembre 2015 14:01

Réponses

  • Voici un exemple de lecture utilisant des Task. Les fichiers lues sont les listes des communes de France de chaque année dans des fichiers txt.

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            'Lancement de la lecture du fichier des communes de France 36658 lignes
            Dim ListesAnnuellesDesCommunes() As String = {"comsimp2015.txt", "comsimp2014.txt", "comsimp2013.txt"}
            'On crée 3 Task, une par fichier à scanner
            Dim TacheLectureFichier(ListesAnnuellesDesCommunes.Length - 1) As Task(Of String)
            'Affichage du début des tâches
            UpdateProgress("Lancement de la lecture des fichiers de 36658 lignes", 0)
    
    
            'Création des tâches
            For ctr As Integer = 0 To ListesAnnuellesDesCommunes.Length - 1
                Dim s As String = ListesAnnuellesDesCommunes(ctr)
                TacheLectureFichier(ctr) = New Task(Of String)(Function()
                                                                   ' Nombre de communes
                                                                   Dim nbCommunes As Integer = 0
                                                                   ' Nom du fichier des communes de France.
                                                                   Dim NomFichier As String = Application.StartupPath & "\" & s
    
                                                                   Dim sr As New StreamReader(NomFichier)
                                                                   'Dim input As String = sr.ReadToEndAsync().Result
                                                                   Dim LigneLue As String
                                                                   Dim ListeDesCommunes As String = "", splitLigne() As String
                                                                   Do While sr.Peek() >= 0
                                                                       'Lecture ligne à ligne pour "ralentir" le process et illustrer les task et ProgressBar
                                                                       LigneLue = sr.ReadLine()
                                                                       splitLigne = LigneLue.Split(vbTab)
                                                                       ListeDesCommunes &= ";" & splitLigne(9)
                                                                   Loop
                                                                   sr.Close()
    
                                                                   sr.Close()
                                                                   'nWords = Regex.Matches(input, pattern).Count
                                                                   Return ListeDesCommunes
                                                               End Function)
            Next
    
            ' Vérification que les fichiers existent avant de lancer les tâches
            Dim allExist As Boolean = True
            For Each Annee In ListesAnnuellesDesCommunes
                Dim fn As String = Application.StartupPath & "\" & Annee
                If Not File.Exists(fn) Then
                    allExist = False
                    MessageBox.Show("Ne trouve pas '{0}'", fn)
                    Exit For
                End If
            Next
            ' Lancement des 3 tâches avec ProgressBar
            If allExist Then
                'For Each t In TacheLectureFichier
                '    t.Start()
                'Next
                TacheLectureFichier(0).Start()
                TacheLectureFichier(0).Wait()
                'Test de complétion: inutile mais cela vérifie que tout est ok
                If TacheLectureFichier(0).IsCompleted = True Then
                    UpdateProgress("Tâche 1 :" & ListesAnnuellesDesCommunes(0), CInt(100 * 1 / 3))
                    Me.Refresh()
                    'DoEvents est déconseillé, mais dans ce cas cela permet de récupérer la lenteur du ProgressBar
                    Application.DoEvents()
                End If
    
                TacheLectureFichier(1).Start()
                TacheLectureFichier(1).Wait()
                If TacheLectureFichier(1).IsCompleted = True Then
                    UpdateProgress("Tâche 2 :" & ListesAnnuellesDesCommunes(1), CInt(100 * 2 / 3))
                    Me.Refresh()
                    Application.DoEvents()
                End If
    
                TacheLectureFichier(2).Start()
                TacheLectureFichier(2).Wait()
                If TacheLectureFichier(2).IsCompleted = True Then
                    UpdateProgress("Tâche 3 :" & ListesAnnuellesDesCommunes(2), CInt(100 * 3 / 3))
                    Me.Refresh()
                    Application.DoEvents()
                End If
    
                'Task.WaitAll(TacheLectureFichier)
    
                For ctr As Integer = 0 To ListesAnnuellesDesCommunes.Length - 1
                    Debug.Print("Année " & ListesAnnuellesDesCommunes(ctr))
                    Debug.Print(TacheLectureFichier(ctr).Result)
                Next
            End If
        End Sub

    J'ai également bloqué le Thread qui affiche la ProgressBar avec Thread.Sleep(1000) car la barre se rafraichie trop lentement pour l'affichage...

    Private Delegate Sub UpdateProgressDelegate(ByVal strMsg As String, ByVal intPercentage As Integer)
    
        Public Sub UpdateProgress(ByVal strMsg As String, ByVal intPercentage As Integer)
            If Me.InvokeRequired Then
                Me.Invoke(New UpdateProgressDelegate(AddressOf UpdateProgress),
                          New Object() {strMsg, intPercentage})
            Else
                If strMsg <> "" Then
                    Me.Label2.Visible = True
                    Me.Label2.Text = strMsg
                End If
    
                If intPercentage >= Me.ProgressBar1.Minimum AndAlso
                    intPercentage <= Me.ProgressBar1.Maximum Then
                    Me.ProgressBar1.Value = intPercentage
                    Me.ProgressBar1.Update()
                    Me.Refresh()
                    Threading.Thread.Sleep(1000)
                End If
            End If
    'Peut être que le DoEvents serait mieux ici que dans la sub appelante?
            'System.Windows.Forms.Application.DoEvents()
        End Sub

    Je pense que cela illustre des solutions à vos problèmes. Si oui indiquez que c'est une solution.


    Cyrille Precetti

    mardi 22 décembre 2015 00:57
  • Cf ma réponse ici pour éviter une redondance

    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 22 décembre 2015 07:25

Toutes les réponses

  • Plusieurs questions à la fois et complexe.

    Question 3: Mais voici un exemple de ProgressBar:

    C'est fait avec le contrôle ProgressBar et avec un Delegate si sur une forme de message qui informe sur votre tâche principale:

     Private Delegate Sub UpdateProgressDelegate(ByVal strMsg As String, ByVal intPercentage As Integer)
    
        Public Sub UpdateProgress(ByVal strMsg As String, ByVal intPercentage As Integer)
            If Me.InvokeRequired Then
                Me.Invoke(New UpdateProgressDelegate(AddressOf UpdateProgress),
                          New Object() {strMsg, intPercentage})
            Else
                If strMsg <> "" Then
                    Me.Label2.Visible = True
                    Me.Label2.Text = strMsg
                End If
    
                If intPercentage >= Me.ProgressBar1.Minimum AndAlso
                    intPercentage <= Me.ProgressBar1.Maximum Then
                    Me.ProgressBar1.Value = intPercentage
                End If
            End If
            System.Windows.Forms.Application.DoEvents()
        End Sub

    Pour les lectures de fichiers asynchrone, la classe System.Threading.Task semble correcte. Il me semble que lorsque que vous lancer une Task cela se passe de façon asynchrone (Question 1: Asynchrone par définition)

    Question 2: Pour renvoyer des valeurs, utilisez la classe Task(Of TResult) (https://msdn.microsoft.com/en-us/library/dd321424%28v=vs.110%29.aspx)


    Cyrille Precetti

    Votez et marquez comme utile si cela vous a servi...
    lundi 21 décembre 2015 14:41
  • Bonjour et merci pour votre aide.

    Mais j'ai besoin d'en savoir plus!

    Merci pour la progressBar.

    Quelle est la réponse à ma question :

    La procédure LireFichierSource est définie comme synchrone (sans Async) et les fonction de lecture son également synchrone. Il y a t il une différence de rapidité de traitement si je la transforme en Asynchrone ?

    OUi j'ai bien vu dans l'aide MSDN qu'il fallait utiliser Task(Of TResult) pour un retour de résultat, mais je n'ai pas réussi à trouver la bonne syntaxe pour un objet retourné = SortedList(Of String, Toponymes)

    Encore merci

    Bernard

    lundi 21 décembre 2015 15:54
  • Re bonjour

    J'ai implanté la procédure concernant la ProgressBar

    Le programme s'arrete à la ligne :

     Me.Invoke(New UpdateProgressDelegate(AddressOf UpdateProgress),
                          New Object() {strMsg, intPercentage})

    sans message d'erreur et bloque le programme

    Pour le premier appel strMsg ="" et intPercentage=0

    Merci

    Bernard

    lundi 21 décembre 2015 16:37
  • Le processus Me.Invoke attend que le Thread principal soit inoccupé pour reprendre son action.

    Si le Invoke bloque c'est qu'ailleurs le Thread principal est bloqué en attente de quelque chose.

    Pour valider votre code de ProgressBar vous devez découpler tout le reste et simuler l'avancement de vos tâches. Lorsque vous aurez validé cela recouplez les autres tâches et aller voir ce qui bloque le Thread principal, probablement une attente de vos Task.

    Un des points probable est le code que vous avez montré:

    While tLireFichierSource.Status <> TaskStatus.RanToCompletion
    End While

    Testez si le RanToCompletion fini bien....


    Cyrille Precetti

    lundi 21 décembre 2015 22:36
  • Voici un exemple de lecture utilisant des Task. Les fichiers lues sont les listes des communes de France de chaque année dans des fichiers txt.

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            'Lancement de la lecture du fichier des communes de France 36658 lignes
            Dim ListesAnnuellesDesCommunes() As String = {"comsimp2015.txt", "comsimp2014.txt", "comsimp2013.txt"}
            'On crée 3 Task, une par fichier à scanner
            Dim TacheLectureFichier(ListesAnnuellesDesCommunes.Length - 1) As Task(Of String)
            'Affichage du début des tâches
            UpdateProgress("Lancement de la lecture des fichiers de 36658 lignes", 0)
    
    
            'Création des tâches
            For ctr As Integer = 0 To ListesAnnuellesDesCommunes.Length - 1
                Dim s As String = ListesAnnuellesDesCommunes(ctr)
                TacheLectureFichier(ctr) = New Task(Of String)(Function()
                                                                   ' Nombre de communes
                                                                   Dim nbCommunes As Integer = 0
                                                                   ' Nom du fichier des communes de France.
                                                                   Dim NomFichier As String = Application.StartupPath & "\" & s
    
                                                                   Dim sr As New StreamReader(NomFichier)
                                                                   'Dim input As String = sr.ReadToEndAsync().Result
                                                                   Dim LigneLue As String
                                                                   Dim ListeDesCommunes As String = "", splitLigne() As String
                                                                   Do While sr.Peek() >= 0
                                                                       'Lecture ligne à ligne pour "ralentir" le process et illustrer les task et ProgressBar
                                                                       LigneLue = sr.ReadLine()
                                                                       splitLigne = LigneLue.Split(vbTab)
                                                                       ListeDesCommunes &= ";" & splitLigne(9)
                                                                   Loop
                                                                   sr.Close()
    
                                                                   sr.Close()
                                                                   'nWords = Regex.Matches(input, pattern).Count
                                                                   Return ListeDesCommunes
                                                               End Function)
            Next
    
            ' Vérification que les fichiers existent avant de lancer les tâches
            Dim allExist As Boolean = True
            For Each Annee In ListesAnnuellesDesCommunes
                Dim fn As String = Application.StartupPath & "\" & Annee
                If Not File.Exists(fn) Then
                    allExist = False
                    MessageBox.Show("Ne trouve pas '{0}'", fn)
                    Exit For
                End If
            Next
            ' Lancement des 3 tâches avec ProgressBar
            If allExist Then
                'For Each t In TacheLectureFichier
                '    t.Start()
                'Next
                TacheLectureFichier(0).Start()
                TacheLectureFichier(0).Wait()
                'Test de complétion: inutile mais cela vérifie que tout est ok
                If TacheLectureFichier(0).IsCompleted = True Then
                    UpdateProgress("Tâche 1 :" & ListesAnnuellesDesCommunes(0), CInt(100 * 1 / 3))
                    Me.Refresh()
                    'DoEvents est déconseillé, mais dans ce cas cela permet de récupérer la lenteur du ProgressBar
                    Application.DoEvents()
                End If
    
                TacheLectureFichier(1).Start()
                TacheLectureFichier(1).Wait()
                If TacheLectureFichier(1).IsCompleted = True Then
                    UpdateProgress("Tâche 2 :" & ListesAnnuellesDesCommunes(1), CInt(100 * 2 / 3))
                    Me.Refresh()
                    Application.DoEvents()
                End If
    
                TacheLectureFichier(2).Start()
                TacheLectureFichier(2).Wait()
                If TacheLectureFichier(2).IsCompleted = True Then
                    UpdateProgress("Tâche 3 :" & ListesAnnuellesDesCommunes(2), CInt(100 * 3 / 3))
                    Me.Refresh()
                    Application.DoEvents()
                End If
    
                'Task.WaitAll(TacheLectureFichier)
    
                For ctr As Integer = 0 To ListesAnnuellesDesCommunes.Length - 1
                    Debug.Print("Année " & ListesAnnuellesDesCommunes(ctr))
                    Debug.Print(TacheLectureFichier(ctr).Result)
                Next
            End If
        End Sub

    J'ai également bloqué le Thread qui affiche la ProgressBar avec Thread.Sleep(1000) car la barre se rafraichie trop lentement pour l'affichage...

    Private Delegate Sub UpdateProgressDelegate(ByVal strMsg As String, ByVal intPercentage As Integer)
    
        Public Sub UpdateProgress(ByVal strMsg As String, ByVal intPercentage As Integer)
            If Me.InvokeRequired Then
                Me.Invoke(New UpdateProgressDelegate(AddressOf UpdateProgress),
                          New Object() {strMsg, intPercentage})
            Else
                If strMsg <> "" Then
                    Me.Label2.Visible = True
                    Me.Label2.Text = strMsg
                End If
    
                If intPercentage >= Me.ProgressBar1.Minimum AndAlso
                    intPercentage <= Me.ProgressBar1.Maximum Then
                    Me.ProgressBar1.Value = intPercentage
                    Me.ProgressBar1.Update()
                    Me.Refresh()
                    Threading.Thread.Sleep(1000)
                End If
            End If
    'Peut être que le DoEvents serait mieux ici que dans la sub appelante?
            'System.Windows.Forms.Application.DoEvents()
        End Sub

    Je pense que cela illustre des solutions à vos problèmes. Si oui indiquez que c'est une solution.


    Cyrille Precetti

    mardi 22 décembre 2015 00:57
  • Cf ma réponse ici pour éviter une redondance

    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 22 décembre 2015 07:25
  • Bonjour Richard

    Merci pour votre aide précieuse.

    Bernard

    mardi 22 décembre 2015 08:47
  • Bonjour Cyrille

    Merci pour votre aide précieuse.

    Bernard

    mardi 22 décembre 2015 08:48
  • Ca fait plaisir d'avoir des remerciements ;-)

    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 22 décembre 2015 09:05