2010-04-16 13 views
3

J'ai un code aussi bienopérations emboîtées multithread traçage

void ExecuteTraced(Action a, string message) 
{ 
    TraceOpStart(message); 
    a(); 
    TraceOpEnd(message); 
} 

Le rappel (a) pourrait appeler ExecuteTraced à nouveau, et, dans certains cas, de manière asynchrone (via ThreadPool, BeginInvoke, PLINQ etc, donc j'ai aucune possibilité de marquer explicitement la portée de l'opération). Je veux tracer toutes les opérations imbriquées (même si elles fonctionnent de manière asynchrone). Donc, j'ai besoin de la possibilité d'obtenir la dernière opération tracée dans un contexte d'appel logique (il peut y avoir beaucoup de threads concurrents, il est donc impossible d'utiliser le champ static lastTraced).

Il existe CallContext.LogicalGetData et CallContext.LogicalSetData, mais malheureusement, LogicalCallContext propage les modifications au contexte parent lorsque EndInvoke() est appelé. Pire encore, cela peut se produire à tout moment si EndInvoke() était appelé async. EndInvoke changes current CallContext - why?

En outre, il est Trace.CorrelationManager, mais basé sur CallContext et ont tous les mêmes problèmes. Il existe une solution de contournement: utilisez la propriété CallContext.HostContext qui ne se propage pas en tant qu'opération async terminée. Retour au début | Envoyer des commentaires Résolution Résolution En outre, il ne clone pas, donc la valeur devrait être immuable - pas un problème. Cependant, il est utilisé par HttpContext et ainsi, solution de contournement n'est pas utilisable dans les applications Asp.Net. La seule façon que je vois est d'enrouler HostContext (sinon le mien) ou LogicalCallContext entier en dynamique et d'expédier tous les appels à côté de la dernière opération tracée.

Répondre

6

Ok, je me réponds moi-même.

Courte: il n'y a pas de solution.

détaillées légèrement:

Le problème est, il me faut un moyen de stocker la dernière opération active par chaque contexte logique. Le code de suivi n'aura aucun contrôle sur le flux d'exécution, il est donc impossible de passer lastStartedOperation en tant que paramètre. Le contexte d'appel peut cloner (par exemple, si un autre thread a démarré), j'ai donc besoin de cloner la valeur en tant que clones de contexte. CallContext.LogicalSetData() convient bien, mais il fusionne les valeurs dans le contexte d'origine lorsque l'opération asynchrone est terminée (en effet, en remplaçant toutes les modifications effectuées avant l'appel de EndInvoke). Theortically, il peut se produire même de manière asynchrone, donnant le résultat imprévisible de CallContext.LogicalGetData().

Je dis théoriquement parce que l'appel simple a.EndInvoke() à l'intérieur d'un asyncCallback ne remplace pas les valeurs dans le contexte d'origine. Cependant, je n'ai pas vérifié le comportement des appels à distance (et il semble que WCF n'honore pas du tout CallContext). En outre, le documentation (ancien) dit:

La méthode BeginInvoke passe le CallContext au serveur. Lorsque la méthode EndInvoke est appelée, le CallContext est fusionné sur l'unité d'exécution . Cela inclut les cas dans lesquels BeginInvoke et EndInvoke sont appelés séquentiellement et où BeginInvoke est appelé sur un thread et EndInvoke est appelé sur une fonction de rappel.

La dernière version est pas définie:

La méthode BeginInvoke passe le CallContext au serveur. Lorsque la méthode EndInvoke est appelée, les données contenues dans le CallContext est copié sur le thread qui a appelé BeginInvoke.

Si vous digg dans la source-cadre, vous trouverez que les valeurs sont effectivement stockées dans une table de hachage à l'intérieur LogicalCallContext à l'intérieur ExecutionContext actuelle du thread courant.

Lors de l'appel de contextes clones (par exemple, sur BeginInvoke) appelé LogicalCallContext.Clone. Et EndInvoke (au moins lorsqu'il est appelé à l'intérieur de CallContext d'origine) appelle LogicalCallContext.Merge() en remplaçant les anciennes valeurs dans m_Datastore par de nouvelles.

Nous avons donc besoin de fournir la valeur qui sera clonée mais pas fusionnée. LogicalCallContext.Clone() clone également (sans fusionner) le contenu de deux champs privés, m_RemotingData et m_SecurityData. Comme les types du champ sont définis comme internes, vous ne pouvez pas en dériver (même avec emit), ajouter la propriété MyNoFlowbackValue et remplacer la valeur du champ m_RemotingData (ou un autre) par l'instance de la classe dérivée.

De même, les types de champs ne sont pas dérivés du MBR, il est donc impossible de les envelopper à l'aide d'un proxy transparent.

Vous ne pouviez pas hériter de LogicalCallContext - il est scellé. (NB en fait, vous pourriez - si vous utilisez l'api de profilage CLR pour remplacer IL comme le font les frameworks fictifs.)

Vous ne pouvez pas remplacer la valeur de m_Datastore, car LogicalCallContext ne sérialise que le contenu de la hashtable, pas la hashtable. lui-même.

La dernière solution consiste à utiliser CallContext.HostContext. Cela stocke efficacement les données dans le champ m_hostContext du LogicalCallContext. LogicalCallContext.Clone() partage (pas clone) la valeur de m_hostContext, donc la valeur doit être immuable. Pas un problème cependant.

Et même cela échoue si HttpContext est utilisé, car il définit la propriété CallContext.HostContext en remplaçant votre ancienne valeur. Ironiquement, HttpContext n'implémente pas ILogicalThreadAffinative, et par conséquent n'est pas stocké en tant que valeur du champ m_hostContext. Il remplace simplement l'ancienne valeur par null. Donc, il n'y a pas de solution et ne le sera jamais, car CallContext est la partie de l'accès distant et distant est obsolète.

P.S. Thace.CorrelationManager utilise CallContext en interne et ne fonctionne donc pas comme vous le souhaitez. BTW, LogicalCallContext a une solution de contournement spéciale pour cloner la pile d'opérations de CorrelationManager sur le clone de contexte. Malheureusement, il n'a pas de solution de rechange spéciale sur la fusion. Parfait!

P.P.S. L'échantillon:

static void Main(string[] args) 
{ 
    string key = "aaa"; 
    EventWaitHandle asyncStarted = new AutoResetEvent(false); 
    IAsyncResult r = null; 

    CallContext.LogicalSetData(key, "Root - op 0"); 
    Console.WriteLine("Initial: {0}", CallContext.LogicalGetData(key)); 

    Action a =() => 
    { 
     CallContext.LogicalSetData(key, "Async - op 0"); 
     asyncStarted.Set(); 
    }; 
    r = a.BeginInvoke(null, null); 

    asyncStarted.WaitOne(); 
    Console.WriteLine("AsyncOp started: {0}", CallContext.LogicalGetData(key)); 

    CallContext.LogicalSetData(key, "Root - op 1"); 
    Console.WriteLine("Current changed: {0}", CallContext.LogicalGetData(key)); 

    a.EndInvoke(r); 
    Console.WriteLine("Async ended: {0}", CallContext.LogicalGetData(key)); 

    Console.ReadKey(); 
} 
+1

Merci pour l'écriture très détaillée, des informations très utiles pour moi en ce moment, et je reconnais qu'il est difficile à trouver! Merci de l'avoir posté –