mercoledì 2 novembre 2011

[VB.NET] MultiThreading

Descrizione :
Semplice esempio pratico di processo che sfrutta il Multithreading in Applicazione Windows Forms.

+ Articolo :

Premessa : il Threading non deve essere inteso come "una pezza" da mettere ad un algoritmo mediocre per "farlo andare più veloce".
L'ottimizzazione in fase di analisi, a monte del codice, è comunque insostituibile.

Qui di seguito faccio un esempio base, un po' "inventato", semplice ma non banale ( io per primo detesto i "tutorial" che "scoprono l'acqua calda"  ).

Poniamo di avere una semplice FormMain, Oggetto di avvio dell'Applicazione.
Le Operazioni da eseguire sono 2 :

  'Operazione1
  'Trova tutti i numeri tra min e max che contengono tutte le cifre "1234567"
  'I numeri trovati vengono inseriti in una List.

  'Operazione2
  'Trova tutti i numeri tra min e max che contengono tutte le cifre "4567890"
  'I numeri trovati vengono inseriti in una List.

Salta subito all'occhio che le 2 Operazioni viaggiano su due insiemi numerici diversi.
Come prima, rozza "ottimizzazione" min1 è inutile che sia inferiore a 1234567, come min2 è inutile che sia inferiore a 4056789.
A meno di non voler considerare anche i negativi, non troverei corrispondenze.

TEST 1.
In un'ottica tipicamente "Form" potrei risolverla semplicemente così :

--> Dichiarazioni Form :
Public Class FormMain
 
    Private ts As TimeSpan
    Private startDate As DateTime
    Private risultatoOp1 As New List(Of Long)
    Private risultatoOp2 As New List(Of Long)
 
...
...

--> Test 1 :
    Private Sub cmd_test1_Click(sender As System.Object, e As System.EventArgs) Handles cmd_test1.Click
 
        startDate = DateTime.Now
 
        'Operazione1
        'Trova tutti i numeri tra min e max che contengono tutte le cifre "1234567"
        'I numeri trovati vengono inseriti in una List.
 
        risultatoOp1.Clear()
 
        Dim s1 As String = "1234567"
        Dim test1 As Boolean
        Dim min1 As Long = 1234567
        Dim max1 As Long = 7654321
 
        For i As Long = min1 To max1
 
            test1 = True
 
            For j As Integer = 0 To s1.Length - 1
 
                If Not i.ToString.Contains(s1(j)) Then
 
                    test1 = False
                    Exit For
 
                End If
 
            Next
 
            If test1 = True Then risultatoOp1.Add(i)
 
        Next
 
        'Operazione2
        'Trova tutti i numeri tra min e max che contengono tutte le cifre "4567890"
        'I numeri trovati vengono inseriti in una List.
 
        risultatoOp2.Clear()
 
        Dim s2 As String = "4567890"
        Dim test2 As Boolean
        Dim min2 As Long = 4056789
        Dim max2 As Long = 9876540
 
        For i As Long = min2 To max2
 
            test2 = True
 
            For j As Integer = 0 To s2.Length - 1
 
                If Not i.ToString.Contains(s2(j)) Then
 
                    test2 = False
                    Exit For
 
                End If
 
            Next
 
            If test2 = True Then risultatoOp2.Add(i)
 
        Next
 
        ts = DateTime.Now - startDate
        MessageBox.Show("Operazione completata in : " & ts.TotalSeconds.ToString("#.0") & " sec.")
 
    End Sub

--> Conclusioni Test 1 :

--> Il thread è unico : lo stesso Main Thread dell'applicazione.
--> FormMain e relativi controlli non si possono usare durante il processo.
--> Tempo Operazione : circa 13 sec. ( sul PC "X" ).

TEST 2.
Cambio strada : adesso voglio che la UI sia libera durante il processo, e inoltre voglio eseguire Op1 e Op2 su due thread separati che riportano a FormMain i risultati.

Tengo la spiegazione all'osso, e preferisco ( come sempre ) postare codice funzionante.
Lascio al lettore l'onere di farsi una cultura su Delegates, Invoke, Threads, Classi, Puntatori, AddressOf, ByRef, ecc...

--> Dichiarazioni Form :
Public Class FormMain
 
    Private ts As TimeSpan
    Private startDate As DateTime
    Private risultatoOp1 As New List(Of Long)
    Private risultatoOp2 As New List(Of Long)
    Private checkRisOps(1) As Boolean
 
    Delegate Sub DelRisOp1(ByVal rOp As List(Of Long))
    Public delegateRisultatoOp1 As New DelRisOp1(AddressOf SetRisultatoOp1)
    Private Sub SetRisultatoOp1(ByVal rOp As List(Of Long))
        risultatoOp1 = rOp
        checkRisOps(0) = True
        CheckRisultati()
    End Sub
 
    Delegate Sub DelRisOp2(ByVal rOp As List(Of Long))
    Public delegateRisultatoOp2 As New DelRisOp1(AddressOf SetRisultatoOp2)
    Private Sub SetRisultatoOp2(ByVal rOp As List(Of Long))
        risultatoOp2 = rOp
        checkRisOps(1) = True
        CheckRisultati()
    End Sub
 
    Private Sub CheckRisultati()
        If Not checkRisOps.Contains(False) Then
            ts = DateTime.Now - startDate
            MessageBox.Show("Operazione completata in : " & ts.TotalSeconds.ToString("#.0") & " sec.")
        End If
    End Sub
 
...
...

--> Operazioni :
Questa volta non ho più un processo continuo "dietro il Button", ma inserisco le Operazioni in una Classe :
Public Class Operazioni
 
    Private m_min As Long
    Private m_max As Long
    Private m_caller As FormMain
 
    Public Sub New(ByRef caller As FormMain, ByVal min As Long, ByVal max As Long)
 
        m_caller = caller
        m_min = min
        m_max = max
 
    End Sub
 
    Public Sub Operazione1()
        'Operazione1
        'Trova tutti i numeri tra min e max che contengono tutte le cifre "1234567"
        'I numeri trovati vengono inseriti in una List.
 
        Dim s As String = "1234567"
        Dim test As Boolean
        Dim lstOp1 As New List(Of Long)
 
        For i As Long = m_min To m_max
 
            test = True
 
            For j As Integer = 0 To s.Length - 1
 
                If Not i.ToString.Contains(s(j)) Then
 
                    test = False
                    Exit For
 
                End If
 
            Next
 
            If test = True Then lstOp1.Add(i)
 
        Next
 
        'Passaggio del Risultato Operazione1 al Thread Chiamante
        m_caller.Invoke(m_caller.delegateRisultatoOp1, lstOp1)
 
    End Sub
 
    Public Sub Operazione2()
        'Operazione2
        'Trova tutti i numeri tra min e max che contengono tutte le cifre "4567890"
        'I numeri trovati vengono inseriti in una List.
 
        Dim s As String = "4567890"
        Dim test As Boolean
        Dim lstOp2 As New List(Of Long)
 
        For i As Long = m_min To m_max
 
            test = True
 
            For j As Integer = 0 To s.Length - 1
 
                If Not i.ToString.Contains(s(j)) Then
 
                    test = False
                    Exit For
 
                End If
 
            Next
 
            If test = True Then lstOp2.Add(i)
 
        Next
 
        'Passaggio del Risultato Operazione1 al Thread Chiamante
        m_caller.Invoke(m_caller.delegateRisultatoOp2, lstOp2)
 
    End Sub
 
End Class

--> Test 2 :
    Private Sub cmd_test2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmd_test2.Click
 
        startDate = DateTime.Now
 
        'Operazione1
        risultatoOp1.Clear()
        checkRisOps(0) = False
        Dim min1 As Long = 1234567
        Dim max1 As Long = 7654321
 
        Dim TOp1 As New Threading.Thread(AddressOf New Operazioni(Me, min1, max1).Operazione1)
        TOp1.IsBackground = True
        TOp1.Start()
 
        'Operazione2
        risultatoOp2.Clear()
        checkRisOps(1) = False
        Dim min2 As Long = 4056789
        Dim max2 As Long = 9876540
 
        Dim TOp2 As New Threading.Thread(AddressOf New Operazioni(Me, min2, max2).Operazione2)
        TOp2.IsBackground = True
        TOp2.Start()
 
    End Sub

--> Conclusioni Test 2 :

--> 3 Threads in gioco : il Main Thread dell'applicazione + 2 Threads esterni.
--> FormMain e relativi controlli si possono continuare ad usare durante il processo.
--> Tempo Operazione : circa 7 sec. ( sempre sul PC "X" ).
--> Direi che ne è valsa decisamente la pena...

+ Fine Articolo.

Un Click su "Mi Piace" è il modo migliore per ringraziare l'autore di questo articolo.



0 commenti:

Posta un commento

 
Design by Free WordPress Themes Modificato da MarcoGG