Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Multithread et Codedom.compiler...

5 réponses
Avatar
Mikado
Salut à tous,

Je démarre plusieurs threads via des QueueUserWorkItem. Dans chaque thread,
je souhaite compiler une assembly via system.codedom.compiler. Le résultat
obtenu (quand il y a résultat) n'est pas associé au bon thread. Comment
faire pour que le thread attende la compilation de l'assembly et l'éxécution
de la méthode et surtout pour qu'un autre thread n'y accède pas ? J'ai
essayer via un monitor mais ça ne fonctionne pas. Quelqu'un à une idée ?

Merci

Mikado

5 réponses

Avatar
adebaene
Mikado wrote:
Salut à tous,

Je démarre plusieurs threads via des QueueUserWorkItem.


Ca ne démarre pas des threads : ca postes du travail à faire pour le
pool de thread qui *éventuellement* démarrera de nouveaux threads (ou
réutilisera des threads existants).

Dans chaque thread,
je souhaite compiler une assembly via system.codedom.compiler. Le résul tat
obtenu (quand il y a résultat) n'est pas associé au bon thread.


????? Quel résultat n'est pas "associé" au bon thread?
QueueUserWorkItem prend comme delegate un WaitCallback, qui spécifie
un type de retour void, donc il n'y a pas de valeur de retour. Tu
cherches à récupérer quoi au juste?

Comment
faire pour que le thread attende la compilation de l'assembly et l'éx écution
de la méthode et surtout pour qu'un autre thread n'y accède pas ? J'ai
essayer via un monitor mais ça ne fonctionne pas. Quelqu'un à une id ée ?


Décris plus clairement ton problème si tu veux une aide précise.

Arnaud
MVP - VC
Avatar
Mikado
Salut Arnaud,

A vouloir faire trop simpliste ça finit par plus vouloir rien dire. En gros,
voici un peu le comportement dont j'ai besoin (c'est vraiment un exemple le
code réel est beaucoup plus long)... une première procédure contient le
genre de code suivant (je mets pas toutes les déclarations pour simplifier)
:

Dim UnSafeUsers As New Queue
Dim Users as Queue
Users = Users.Synchronized(UnSafeUsers) 'Je rends thread safe une Collection
d'utilisateurs

For User = 1 To 100
Users.Enqueue = "Utilisateur" & User 'Je mets en queue 100 utilisateurs
Next

For Thread = 0 To 10 'Je souhaite déclencher 10 threads (à la condition bien
sûr que le pool est 10 threads de libre)
'Les threads déclenchés doivent dequeue les 100 utilisateurs. En théorie
chaque thread devrait dequeue 10 utilisateurs
EventThread(Thread) = New ManualResetEvent(False)
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf
MaClasse.MaFonction), New Params(Template, Users, EventThread(Thread))
Next
If WaitHandle.WaitAll(EventThread) Then Trace.writeline("Ok s'est terminé")

La classe Params me permet de passer les différents paramètres à ma fonction
via des champs. Ils tous passés par reférence.

Public Class MaClasse
Public Count As Integer = 0 'Je veux compter le nombre d'utilisateur qui
ont été dequeue

Public Sub MaFonction(ByVal Args As Object)
Args = CType(Args, Params) 'Je récupère les différentes valeurs
passées en paramètres via la classe Params
Dim Template as String = Args.Template
Dim Users as Queue = Args.Users
Dim EventThread as ManualResetEvent = Args.EventThread
Dim Result as string

Do While Users.Count > 0 'Tant que les threads n'ont pas terminé de
vider la queue users...
Result = Users.Dequeue 'Result contient pour le premier
élément Utilisateur1, Utilisateur2 pour le second et ainsi de suite...
Interlocked.Increment(Count) 'On incrémente de 1 le nombre
d'utilisateur traité...

Dim CodeProvider As VBCodeProvider = New VBCodeProvider
Dim ICC As ICodeCompiler = CodeProvider.CreateCompiler

Dim Parameters As CompilerParameters = New
CompilerParameters
Parameters.GenerateExecutable = False
Parameters.GenerateInMemory = True

Dim SourceCode As String
SourceCode = "Imports System" & vbCrLf
SourceCode += "Namespace NameSpaceTest" & vbCrLf
SourceCode += " Public Class ClassTest" & vbCrLf
SourceCode += " Function RenderTest(ByVal User as string) as
string" & vbCrLf
SourceCode += " Return ""C'est l'utilisateur : "" & User" &
vbCrLf
SourceCode += " End Function" & vbCrLf
SourceCode += " End Class" & vbCrLf
SourceCode += "End Namespace"

Dim Results As CompilerResults ICC.CompileAssemblyFromSource(Parameters, SourceCode)
Dim Instance As Object Results.CompiledAssembly.CreateInstance("NameSpaceTest.ClassTest")
Dim TType As Type = Instance.GetType
Dim Params As Object() = New Object(0) {}
Params(0) = Result 'Dans mon code Result contient des
données à traiter. Pour cet exemple j'y ai mis le résultat du Dequeue
histoire de montrer le problème

Result = TType.InvokeMember("RenderTest",
Reflection.BindingFlags.InvokeMethod, Nothing, Instance, Params)
Trace.writeline("-->" & Result & " (" & Count & " / " &
Users.count+1 & ")")
Loop
ThreadEvent.Set()
End Sub
End Class

Avec un thread et un utilisateur j'obtiens :
--> C'est l'utilisateur : Utilisateur1 (1 / 1)

Avec un thread et plusieurs utilisateur j'obtiens :
--> Utilisateur1 (1 / 5)
--> Utilisateur2 (2 / 4)
--> Utilisateur3 (3 / 3)
--> Utilisateur4 (4 / 2)
--> Utilisateur5 (5 / 1)

Je suppose qu'il s'agit d'un problème de synchro mais j'arrive pas à le
corriger.

Plus bizarre, avec plusieurs threads et plusieurs utilisateurs j'obtiens :

--> Utilisateur1 (1 / 5)
--> Utilisateur2 (-1 / 4)
--> Utilisateur3 (-2 / 3)
--> Utilisateur4 (0 / 2)
--> Utilisateur5 (2 / 1)

Count ne correspond plus à rien.

Voilà en gros mon problème. Alors docteur... ou est-ce qu'il faut que je
corrige ?

Merci

Mikado
Avatar
adebaene
Mikado wrote:
<snip>>
Do While Users.Count > 0
Result = Users.Dequeue
Interlocked.Increment(Count)


</snip>
Loop




Ceci n'est pas thread-safe du tout, même si Users est une liste
"Synchronized".
Cette idée de collections "Synchronized" est de toute façon foireuse
(AMHA, c'est une mauvaise idée d'avoir introduit çà dans le
framework au départ, ca implique trop de dangers que les gens se
croient synchronisés alors qu'il ne le sont pas du tout).
En effet, quasiement tout le temps, ce que tu veux rendre atomique,
c'est un ensemble de plusieurs opérations sur le conteneur, et pas
simplement une opération. Dans ton cas, il faut synhcroniser
l'ensemble de la boucle (test du nombre d'élements de la queue et
Dequeue). Ca donne quelque chose du genre :

Dim continue_loop as bool = true;
Do While continue_loop
SyncLock Me 'je syncrhonise *toute* l'operation de Dequeue
continue_loop = Users.Count > 0
If continue_loop
Result = Users.Dequeue
Interlocked.Increment(Count)
End If
End SyncLock

' faire le traitement sur Result si continue_loop est vrai.
' Remarques qu'on peut faire ce traitement sans tenir le lock qui ne
' sert qu'à protéger la boucle.

Loop
ThreadEvent.Set()

Désolé pour les fautes de syntaxe (VB n'est vraiement pas ma tasse de
thé), mais ca te donne une idée.

Arnaud
MVP - VC
Avatar
Mikado
Merci Arnaud,

Pas grave pour la syntaxe :)

Tu peux m'expliquer pourquoi l'idée de collections "Sychronized" est
foireuse ?

Bon sinon pour le code ça fonctionne toujours pas. InvokeMember, c'est pas
une méthode asynchrone ça ?

Jérôme
Avatar
Arnaud Debaene
Mikado wrote:
Merci Arnaud,

Pas grave pour la syntaxe :)

Tu peux m'expliquer pourquoi l'idée de collections "Sychronized" est
foireuse ?



Dans ton code original :
Do While Users.Count > 0
Result = Users.Dequeue
Interlocked.Increment(Count)



Le fait que la liste soit Synchronized garantit que l'appel à Users.Count
est atomique (aucun n'autre thread ne manipulera la liste pendant cet
appel), et garantit que Users.Dequeue sera atomique (même garantie).

Par contre, rien ne garantit qu'un aute thread ne va pas manipuler la liste
(ajouter/supprimer/modifier un élément) entre l'appel à Users.Count et
l'appel à Users.Dequeue (sans parler de l'increment de Count qui fait aussi
partie logiquement de "l'opération atomique" telle qu'elle est dans ton
algorithme). C'est pour çà que dans mon code, je protège l'ensemble de ces 3
opérations par un lock De manière générale, toute opération non triviale sur
une objet réclame plusieurs appels de méthodes, et donc la synchronisation
est nécessairement à l'extérieur de l'objet manipulé.

Bon sinon pour le code ça fonctionne toujours pas. InvokeMember,
c'est pas une méthode asynchrone ça ?


Je n'ai pas regardé le traitement de ton Result, je me suis concentré sur
l'erreur de synchro dans la Queue. Commences par faire tourner ton
traitement de compilation 1 seule fois, sans t'encombrer du threading pour
valider ce que tu fais. Notamment, tu ne vérifies pas les erreurs
éventuelles de CompileAssemblyFromSource, ni si Instance est bien créée.

En tout cas, InvokeMember n'est pas asynchrone (RTFM!). Par contre, vérifie
les bidingFlags que tu utilises pour cet appel : sans tester, il me semble
qu'il faut au moins BindingFlags.InvokeMethod | BindingFlags.Public |
BindingFlags.Instance

Arnaud
MVP - VC