2009-10-29 4 views
1

explication que je peux Simplest produire:verrouillage utilisé dans le rappel du cache d'objet et une autre méthode ne semble pas verrouiller

Dans mon application web .NET1.1 créer un fichier sur le disque, dans la méthode de rendu, et ajouter un élément dans le cache expirant dans, disons, une minute. J'ai aussi une méthode de rappel, à appeler quand l'élément de cache expire, qui supprime le fichier créé par Render. Dans la méthode Page_Init, j'essaie d'accéder au fichier que la méthode Render a écrite sur le disque. Ces deux méthodes ont une instruction lock, verrouillant un objet statique privé.

Intention:

Pour créer une page qui écrit essentiellement une copie de lui-même sur le disque, ce qui est supprimé avant qu'il ne soit trop vieux (ou hors de ce jour, sage contenu), tout en servant le fichier si elle existe sur le disque.

problème observé:

C'est vraiment deux questions, je pense. Demander la page fait ce que j'attends, elle restitue la page sur le disque et la sert immédiatement, tout en ajoutant l'élément d'expiration au cache. Pour tester le temps d'expiration est de 1 minute.

Je m'attends alors à ce que la méthode de rappel soit appelée après 60 secondes et efface le fichier. Ce n'est pas le cas. Après une autre minute (par souci d'argument), je rafraîchis la page dans le navigateur. Ensuite, je peux voir la méthode de rappel s'appeler et placer un verrou sur l'objet de verrouillage. Le Page_Init est également appelé et place un verrou sur le même objet. Cependant, les deux méthodes semblent entrer leur bloc de code de verrouillage et poursuivre l'exécution. Résultat: Le fichier de vérification de rendu est présent, la méthode de rappel supprime le fichier, la méthode de rendu essaie de servir le fichier maintenant supprimé.

extrait de code Horriblement simplifié:

public class MyPage : Page 
{ 
    private static Object lockObject = new Obect(); 

    protected void Page_Init(...) 
    { 
    if (File.Exists(...)) 
    { 
     lock (lockObject) 
     { 
     if (File.Exists(...)) 
     { 
      Server.Transfer(...); 
     } 
     } 
    } 
    } 

    protected override void Render(...) 
    { 
    If (!File.Exists(...)) 
    { 
     // write file out and serve initial copy from memory 
     Cache.Add(..., new CacheItemRemovedCallback(DoCacheItemRemovedCallback)); 
    } 
    } 

    private static void DoCacheItemRemovedCallback(...) 
    { 
    lock (lockObject) 
    { 
     If (File.Exists(...)) 
     File.Delete(...); 
    } 
    } 
} 

Quelqu'un peut-il expliquer cela, s'il vous plaît? Je comprends que la méthode de rappel est, paresseusement, paresseuse et donc ne rappelle que lorsque je fais une demande, mais sûrement le thread dans .NET1.1 est assez bon pour ne pas laisser deux blocs lock() entrer simultanément?

Merci,

Matt.

+0

Non connexe, mais: 'private static Object lockObject = new Obect()' - sur une page Web? Cela signifie que * toutes * les demandes partagent un verrou - est-ce ce que vous vouliez? –

+0

Peut-être que je ne comprends pas cela correctement, mais il semble que vous dupliquiez la fonctionnalité de la directive OutputCache? Voir http://msdn.microsoft.com/en-us/library/hdxfb6cy(VS.71).aspx – PhilPursglove

+0

[Silence embarrassant] Oui, il semblerait que oui. En fait, j'avais l'impression que .NET1.1 n'était pas disponible. Je vais maintenant me casser la tête et essayer de me rappeler pourquoi et si cela impliquait une faille dans le 1.1 OutputCache. Ensuite, je vais l'utiliser. Merci. [Plus de silence] –

Répondre

1

Je ne sais pas pourquoi votre solution ne fonctionne pas, mais cela pourrait être une bonne chose, compte tenu des conséquences ...

je suggère un itinéraire complètement différent. Séparez le processus de gestion du fichier du processus de demande du fichier.

Les demandes doivent simplement aller dans le cache, obtenir le chemin d'accès complet du fichier et l'envoyer au client.

Un autre processus (non lié aux requêtes) est responsable de la création et de la mise à jour du fichier. Il crée simplement le fichier lors de la première utilisation/accès et stocke le chemin complet dans le cache (défini pour ne jamais expirer). À des intervalles réguliers/appropriés, il recrée le fichier avec un autre nom aléatoire, définit ce nouveau chemin dans le cache, puis supprime l'ancien fichier (en veillant à ce qu'il ne soit pas bloqué par une autre requête).

Vous pouvez générer ce processus de gestion de fichiers au démarrage d'une application à l'aide d'un thread ou du ThreadPool. Lier votre gestion de fichiers et vos requêtes vous causera toujours des problèmes puisque votre processus sera exécuté simultanément, ce qui vous oblige à effectuer une synchronisation de threads qu'il est toujours préférable d'éviter.

+0

Ce que j'ai fini par faire n'était pas de supprimer les fichiers sur une expiration de cache, mais en retirant complètement le cache de l'équation - en vérifiant simplement les tampons LastWrite et LastModified. Cela nécessite que je les ré-estampille lorsque je remplace les fichiers avec des versions plus récentes, parce que je n'ai pas observé de changements dans leurs horodatages dans l'Explorateur, ou lors de la vérification par programme pour un fichier qui avait été écrasé quelques instants auparavant. –

0

La première chose à faire est d'ouvrir la fenêtre Threads et d'observer le thread sur lequel le Page_Init s'exécute et le thread sur lequel le Call Back s'exécute. La seule façon dont je sais que deux méthodes peuvent placer un verrou sur le même objet est si elles s'exécutent dans le même thread.

Modifier

La vraie question ici est de savoir comment Server.Transfer fonctionne réellement. Server.Transfer configure simplement certains détails internes ASP.NET indiquant que la requête est sur le point d'être transférée vers une URL différente sur le serveur. Il appelle ensuite Response.End qui à son tour lève une exception ThreadAbortException. Aucune donnée réelle n'a été lue ou envoyée au client à ce moment-là.

Maintenant, lorsque l'exception se produit, l'exécution du code quitte le bloc de code protégé par le verrou. À ce moment, la fonction de rappel peut acquérir le verrou et supprimer le fichier.

Maintenant quelque part au cœur de ASP.NET ThreadAbortException est traité d'une manière ou d'une autre et la demande pour la nouvelle URL est traitée. À ce moment, il trouve le fichier a disparu.

+0

Ok, merci pour le conseil - je n'ai pas réalisé qu'il y avait une fenêtre de discussion! Quoi qu'il en soit, ayant fait cela, je vois que vous avez raison et qu'il y a deux threads qui peuvent chacun entrer leurs propres blocs d'instructions de verrouillage tout en ayant un verrou sur un objet statique. Cela, je le comprends bien, va à l'encontre de ce que Microsoft dit qu'il devrait faire. Sauf si je suis confondu avec (peut-être) des utilisations disparates du terme «thread»: http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.71).aspx –

+0

Vous n'êtes pas confus. Deux threads différents ne peuvent pas verrouiller le même objet en même temps. Période. Quelque chose d'autre se passe. Ajoutez 'readonly' à l'objet lockObject pour qu'il ne puisse pas être remplacé. Comment réussir à observer ce comportement exactement? – AnthonyWJones

+0

Je ne fais que passer le code. L'observation externe est que la page qui est visiblement mise en cache sur le disque donne une exception lorsque j'essaie de Server.Transfer à elle. Il devrait être là parce que juste avant de le transférer, je vérifie son existence. Cependant, lorsque le transfert a lieu, il est parti, provoquant l'exception. La vue interne du problème montre que les deux instructions de verrouillage s'exécutent simultanément. On devance l'autre quand on demande un objet du cache. Je suppose que c'est là que l'autre thread peut aller de l'avant parce qu'il ne fait pas un appel de cadre lent. –