'Constande définie pour activer la gestion de NHLog ou non
#Const CteNHLog = False

''' <summary>
''' Classe de base pour les threads
''' </summary>
''' <remarks></remarks>
Public MustInherit Class ClsBaseThread

#Region "Shared"
    ''' <summary>
    ''' Mémorise la liste des threads qui sont en cours de fonctionnement
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared msLstThread As New List(Of ClsBaseThread)
    ''' <summary>
    ''' Signale aux threads si il peuvent continuer de fonctionner
    ''' </summary>
    ''' <remarks></remarks>
    Protected Shared msCanRun As Boolean = True

    ''' <summary>
    ''' Ajoute un threads à la liste
    ''' </summary>
    ''' <param name="pObj">Thread à ajouter</param>
    ''' <remarks></remarks>
    Private Shared Sub Add(ByVal pObj As ClsBaseThread)
        msLstThread.Add(pObj)
    End Sub
    ''' <summary>
    ''' Retire un thread de la liste des threads en fonctionnement
    ''' </summary>
    ''' <param name="pObj">Thread à retirer</param>
    ''' <remarks></remarks>
    Private Shared Sub Remove(ByVal pObj As ClsBaseThread)
        msLstThread.Remove(pObj)
    End Sub
    ''' <summary>
    ''' Arrête tous les threads
    ''' </summary>
    ''' <param name="pWait">Permet d'attendre l'arrêt de touys les threads avant de quitter la méthode</param>
    ''' <remarks></remarks>
    Friend Shared Sub StopAllThreads(Optional ByVal pDefinitive As Boolean = True, Optional ByVal pWait As Boolean = False, Optional ByVal pHurry As Boolean = False)
#If CteNHLog Then
        AddLog("ClsBaseThread.StopAllThreads", "Params : Definitive {0}, Wait {1}, Hurry {2}", pDefinitive, pWait, pHurry, EPrio.Information)
#End If
        msCanRun = pDefinitive
        If pWait Then
            'Arrêt de tous threads
            Dim lLst As New List(Of ClsBaseThread)
            lLst.AddRange(msLstThread)
            For Each lObj As ClsBaseThread In lLst
                lObj.StopThread(False, False)
            Next
            'Attente des arrêts
            Do While msLstThread.Count > 0
                Try
                    msLstThread(0).StopThread(True, pHurry)
                    Sleep(1)
                    If msLstThread(0).IsRun = False Then
                        SyncLock msLstThread
                            msLstThread.RemoveAt(0)
                        End SyncLock
                    End If
                Catch ex As Exception
                End Try
            Loop
        Else
            Dim lLst As New List(Of ClsBaseThread)
            lLst.AddRange(msLstThread)
            For Each lObj As ClsBaseThread In lLst
                lObj.StopThread(False, False)
            Next
        End If
    End Sub
    ''' <summary>
    ''' Retourne toutes les objets du type spécifiés
    ''' </summary>
    ''' <param name="Object">Type à rechercher</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Friend Shared Function GetObjects(ByVal [Object] As Type) As List(Of ClsBaseThread)
        Dim lList As New List(Of ClsBaseThread)
        For Each lThread As ClsBaseThread In msLstThread
            If TypeOf lThread Is [Object] Then
                lList.Add(lThread)
            End If
        Next
        Return lList
    End Function
    ''' <summary>
    ''' Retourne toutes les objets du type spécifiés
    ''' </summary>
    ''' <typeparam name="T">Type à rechercher</typeparam>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Friend Shared Function GetObjects(Of T As ClsBaseThread)() As List(Of T)
        Dim lList As New List(Of T)
        For Each lThread As ClsBaseThread In msLstThread
            If TypeOf lThread Is T Then
                lList.Add(CType(lThread, T))
            End If
        Next
        Return lList
    End Function
    ''' <summary>
    ''' Fait une pause
    ''' </summary>
    ''' <param name="pTime">Durée de la pause en millisecondes</param>
    ''' <remarks></remarks>
    Protected Shared Sub Sleep(ByVal pTime As Integer)
        Threading.Thread.Sleep(pTime)
    End Sub
#End Region

#Region "non shared"
    ''' <summary>
    ''' Mémorise l'objet du thread
    ''' </summary>
    ''' <remarks></remarks>
    Private mThread As Threading.Thread
    ''' <summary>
    ''' précise si le thread peut continuer à fonctionner
    ''' </summary>
    ''' <remarks></remarks>
    Protected mCanRun As Boolean = True
    ''' <summary>
    ''' Mémorise l'ID du thread
    ''' </summary>
    ''' <remarks></remarks>
    Private mThID As Integer
    ''' <summary>
    ''' Signale au thread qu'il doit s'arrêter rapidement (5 secondes avant exception ThreadAbortException)
    ''' </summary>
    ''' <remarks></remarks>
    Protected mHurry As Boolean = False

    ''' <summary>
    ''' Constructeur de la classe
    ''' </summary>
    ''' <param name="pThreadName">Nom du thread lancé</param>
    ''' <remarks></remarks>
    Friend Sub New(ByVal pThreadName As String)
        mThread = New Threading.Thread(AddressOf SubThread)
        mThread.Name = pThreadName
        mThread.Start()
    End Sub

    ''' <summary>
    ''' Procédure de gestion intra thread
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub SubThread()
        Dim lRestart As Boolean = False
#If CteNHLog Then
        AddLog("ClsBaseThread.SubThread", "Démarrage du thread : {0}", mThread.Name, EPrio.Information)
#End If
        Add(Me)
        Do
            lRestart = False
            Try
                Threading.Thread.Sleep(500)
                If mCanRun And msCanRun Then
                    ProSubThread()
                End If
            Catch ex As Exception
#If CteNHLog Then
                AddLog("ClsBaseThread.SubThread", "Exception (1) du thread ""{0}"" : {1}", mThread.Name, ex, EPrio.Information)
#End If
                Try
                    If ProErrThread(ex) And msCanRun And mCanRun Then
                        lRestart = True
                    End If
                Catch ex1 As Exception
#If CteNHLog Then
                    AddLog("ClsBaseThread.SubThread", "Exception (2) du thread ""{0}"" : {1}" + vbCrLf + "Origine : {2}", mThread.Name, ex1, ex, EPrio.Avertissement)
#End If
#If DEBUG Then
                    Debug.Print("ErrThread : " + ex.ToString + vbCrLf + ex1.ToString)
#End If
                End Try
            End Try
        Loop While lRestart
        Remove(Me)
#If CteNHLog Then
        AddLog("ClsBaseThread.SubThread", "arrêt du thread : {0}", mThread.Name, EPrio.Information)
#End If
        System.GC.Collect()
    End Sub

    ''' <summary>
    ''' Retourne le HashCode
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides Function GetHashCode() As Integer
        If mThread.IsAlive Then
            mThID = mThread.ManagedThreadId
            Return mThread.ManagedThreadId
        Else
            Return mThID
        End If
    End Function

    ''' <summary>
    ''' Arrête le thread
    ''' </summary>
    ''' <param name="pWait">Si à <c>True</c> attend l'arrêt du thread</param>
    ''' <param name="pHurry">Indique si l'arrêt est urgent (si c'est le cas, le thread a 5 secondes pour s'arrêter)</param>
    ''' <remarks></remarks>
    Friend Overridable Sub StopThread(Optional ByVal pWait As Boolean = False, Optional ByVal pHurry As Boolean = False)
#If CteNHLog Then
        AddLog("ClsBaseThread.StopThread", "Wait {0},Hurry {1}", pWait, pHurry, EPrio.Information)
#End If
        mHurry = pHurry
        mCanRun = False
        If pWait Then
            Dim lInt As Integer = Environment.TickCount
            Do While mThread IsNot Nothing AndAlso mThread.IsAlive
                If pHurry AndAlso Environment.TickCount - 5000 > lInt Then
                    lInt = Environment.TickCount
                    mThread.Abort()
                Else
                    'Application.DoEvents()
                    Threading.Thread.Sleep(100)
                End If
            Loop
        End If
        System.GC.Collect()
    End Sub
    ''' <summary>
    ''' Retourne un bouléen signalant si le thread est toujours en fonctionnement
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Friend ReadOnly Property IsRun() As Boolean
        Get
            Return (mThread IsNot Nothing AndAlso mThread.IsAlive)
        End Get
    End Property

    ''' <summary>
    ''' Retourne le nom du thread
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Name() As String
        Get
            Return mThread.Name
        End Get
    End Property

    ''' <summary>
    ''' Méthode utilisée pour le thread par la classe dérivante
    ''' </summary>
    ''' <remarks></remarks>
    Protected MustOverride Sub ProSubThread()
    ''' <summary>
    ''' Méthode de gestion des erreurs non gérées dans le thread, retourne un bouléne signalant si thread doit être redémarré
    ''' </summary>
    ''' <param name="pEx">Exception non gérée</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected MustOverride Function ProErrThread(ByVal pEx As Exception) As Boolean
#End Region
End Class

''' <summary>
''' Classe permettant la création de threads à partir de méthodes (sans avoir besoin de créer une classe)
''' </summary>
''' <typeparam name="TParam">Type du paramètre pour le thread</typeparam>
''' <remarks></remarks>
Friend NotInheritable Class ClsDelegateThread(Of TParam)
    Inherits ClsBaseThread

    ''' <summary>
    ''' Delegate permettant l'execution du thread
    ''' </summary>
    ''' <param name="pThread">Objet gérant le thread</param>
    ''' <param name="pParams">Paramètres du thread</param>
    ''' <remarks></remarks>
    Public Delegate Sub SubThread(ByVal pThread As ClsDelegateThread(Of TParam), ByVal pParams As TParam)
    ''' <summary>
    ''' Delegate de gestion des exception du thread
    ''' </summary>
    ''' <param name="pThread">Objet gérant le thread</param>
    ''' <param name="pParams">Paramètres du thread</param>
    ''' <param name="pEx">Exception à gérer</param>
    ''' <returns>Return un bouléen indiquent si le thread doit redémarrer</returns>
    ''' <remarks></remarks>
    Public Delegate Function ErrThread(ByVal pThread As ClsDelegateThread(Of TParam), ByVal pParams As TParam, ByVal pEx As Exception) As Boolean

    ''' <summary>
    ''' Mémorise les paramètres du thread
    ''' </summary>
    ''' <remarks></remarks>
    Private mParam As TParam
    ''' <summary>
    ''' Mémorise la procédure du thread
    ''' </summary>
    ''' <remarks></remarks>
    Private mSubThread As SubThread
    ''' <summary>
    ''' Mémorise le gestionnaire d'exception
    ''' </summary>
    ''' <remarks></remarks>
    Private mErrThread As ErrThread

    ''' <summary>
    ''' Constructeur de la classe
    ''' </summary>
    ''' <param name="pThreadName">Nom du thread</param>
    ''' <param name="pSubThread">Procédure du thread</param>
    ''' <param name="pErrThread">Optionnel, gestion des exceptions</param>
    ''' <param name="pParam">Paramètres du thread</param>
    ''' <remarks></remarks>
    ''' <exception cref="ArgumentException">Généré si pSubThread est à null (Nothing en VB)</exception>
    Public Sub New(ByVal pThreadName As String, ByVal pSubThread As SubThread, ByVal pParam As TParam, Optional ByVal pErrThread As ErrThread = Nothing)
        MyBase.New(pThreadName)
        mParam = pParam
        mSubThread = pSubThread
        mErrThread = pErrThread
        If mSubThread Is Nothing Then
            StopThread()
            Throw New ArgumentException("Un paramètres a une valeur null (Nothing en Visual Basic)")
        End If
    End Sub
    ''' <summary>
    ''' Procédure du thread
    ''' </summary>
    ''' <remarks></remarks>
    Protected Overrides Sub ProSubThread()
        mSubThread.Invoke(Me, mParam)
    End Sub
    ''' <summary>
    ''' Gestionnaire des exceptions
    ''' </summary>
    ''' <param name="pEx">Exception à traiter</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Overrides Function ProErrThread(ByVal pEx As System.Exception) As Boolean
        If mErrThread Is Nothing Then
#If CteNHLog Then
            AddLog("ClsDelegateThread(of TParam).ProErrThread", "Exception non gérée du thread ""{0}"" : {1}", Name, pEx, EPrio.Avertissement)
#End If
            Return False
        Else
            Return mErrThread.Invoke(Me, mParam, pEx)
        End If
    End Function

    ''' <summary>
    ''' Permet au thread de savoir s'il peut continuer à fonctionner
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property CanRun() As Boolean
        Get
            Return mCanRun And msCanRun
        End Get
    End Property
    ''' <summary>
    ''' Permet au thread de savoir si son arrêt est urgent
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Hurry() As Boolean
        Get
            Return mHurry
        End Get
    End Property
End Class
''' <summary>
''' Classe permettant la création de threads à partir de méthodes (sans avoir besoin de créer une classe)
''' </summary>
''' <remarks></remarks>
Friend NotInheritable Class ClsDelegateThread
    Inherits ClsBaseThread

    ''' <summary>
    ''' Delegate permettant l'execution du thread
    ''' </summary>
    ''' <param name="pThread">Objet gérant le thread</param>
    ''' <remarks></remarks>
    Public Delegate Sub SubThread(ByVal pThread As ClsDelegateThread)
    ''' <summary>
    ''' Delegate de gestion des exception du thread
    ''' </summary>
    ''' <param name="pThread">Objet gérant le thread</param>
    ''' <param name="pEx">Exception à gérer</param>
    ''' <returns>Return un bouléen indiquent si le thread doit redémarrer</returns>
    ''' <remarks></remarks>
    Public Delegate Function ErrThread(ByVal pThread As ClsDelegateThread, ByVal pEx As Exception) As Boolean

    ''' <summary>
    ''' Mémorise la procédure du thread
    ''' </summary>
    ''' <remarks></remarks>
    Private mSubThread As SubThread
    ''' <summary>
    ''' Mémorise le gestionnaire d'exception
    ''' </summary>
    ''' <remarks></remarks>
    Private mErrThread As ErrThread

    ''' <summary>
    ''' Constructeur de la classe
    ''' </summary>
    ''' <param name="pThreadName">Nom du thread</param>
    ''' <param name="pSubThread">Procédure du thread</param>
    ''' <param name="pErrThread">Optionnle, gestion des exceptions</param>
    ''' <remarks></remarks>
    ''' <exception cref="ArgumentException">Généré si pSubThread ou pErrThread est à null (Nothing en VB)</exception>
    Public Sub New(ByVal pThreadName As String, ByVal pSubThread As SubThread, Optional ByVal pErrThread As ErrThread = Nothing)
        MyBase.New(pThreadName)
        mSubThread = pSubThread
        mErrThread = pErrThread
        If mSubThread Is Nothing Then
            StopThread()
            Throw New ArgumentException("Un paramètres a une valeur null (Nothing en Visual Basic)")
        End If
    End Sub
    ''' <summary>
    ''' Procédure du thread
    ''' </summary>
    ''' <remarks></remarks>
    Protected Overrides Sub ProSubThread()
        mSubThread.Invoke(Me)
    End Sub
    ''' <summary>
    ''' Gestionnaire des exceptions
    ''' </summary>
    ''' <param name="pEx">Exception à traiter</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Overrides Function ProErrThread(ByVal pEx As System.Exception) As Boolean
        If mErrThread Is Nothing Then
#If CteNHLog Then
            AddLog("ClsDelegateThread.ProErrThread", "Exception non gérée du thread ""{0}"" : {1}", Name, pEx, EPrio.Avertissement)
#End If
            Return False
        Else
            Return mErrThread.Invoke(Me, pEx)
        End If
    End Function

    ''' <summary>
    ''' Permet au thread de savoir s'il peut continuer à fonctionner
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property CanRun() As Boolean
        Get
            Return mCanRun And msCanRun
        End Get
    End Property
    ''' <summary>
    ''' Permet au thread de savoir si son arrêt est urgent
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Hurry() As Boolean
        Get
            Return mHurry
        End Get
    End Property
End Class