106 votes

Est-ce que "l'utilisation" de plusieurs ressources peut provoquer une fuite de ressources?

C # me permet de faire ce qui suit (exemple de MSDN):

 using (Font font3 = new Font("Arial", 10.0f),
            font4 = new Font("Arial", 10.0f))
{
    // Use font3 and font4.
}
 

Que se passe-t-il si font4 = new Font jette? D'après ce que je comprends, font3 perdra des ressources et ne sera pas éliminé.

  • Est-ce vrai? (font4 ne sera pas éliminé)
  • Est-ce que cela signifie que using(... , ...) devrait être évité complètement en faveur de l'utilisation imbriquée?

158voto

SLaks Points 391154

Pas de.

Le compilateur va générer un distinct finally bloc pour chaque variable.

La spec (§8.13) dit:

Lorsqu'une acquisition de ressources prend la forme d'un local-variable-de la déclaration, il est possible d'acquérir plusieurs les ressources d'un type donné. Un using déclaration de la forme

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement 

est précisément équivalent à une séquence imbriquée à l'aide des déclarations:

using (ResourceType r1 = e1)
   using (ResourceType r2 = e2)
      ...
         using (ResourceType rN = eN)
            statement

67voto

Eric Lippert Points 300275

Mise à JOUR: j'ai utilisé cette question comme base pour un article qui peut être trouvé ici; voir pour une discussion approfondie de cette question. Merci pour la bonne cause!


Si Schabse la réponse est, bien sûr, correct et répond à la question qui a été posée, il y a une variante importante sur votre question, vous n'avez pas demander:

Ce qui se passe si font4 = new Font() jette après le non géré les ressources allouées par le constructeur, mais avant de le ctor retourne et remplit font4 avec la référence?

Permettez-moi de rendre cela un peu plus clair. Supposons que l'on ait:

public sealed class Foo : IDisposable
{
    private int handle = 0;
    private bool disposed = false;
    public Foo()
    {
        Blah1();
        int x = AllocateResource();
        Blah2();
        this.handle = x;
        Blah3();
    }
    ~Foo()
    {
        Dispose(false);
    }
    public void Dispose() 
    { 
        Dispose(true); 
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (this.handle != 0) 
                DeallocateResource(this.handle);
            this.handle = 0;
            this.disposed = true;
        }
    }
}

Maintenant, nous avons

using(Foo foo = new Foo())
    Whatever(foo);

C'est le même que

{
    Foo foo = new Foo();
    try
    {
        Whatever(foo);
    }
    finally
    {
        IDisposable d = foo as IDisposable;
        if (d != null) 
            d.Dispose();
    }
}

OK. Supposons Whatever lancers. Puis l' finally bloc s'exécute, et la ressource est libéré. Pas de problème.

Supposons Blah1() lancers. Alors le jet qui se passe avant que la ressource est allouée. L'objet a été alloué, mais le ctor ne retourne jamais, alors foo n'est jamais rempli. Nous ne nous sommes jamais entrés dans l' try , nous n'avons jamais entrer dans l' finally . La référence d'objet a été orphelin. Finalement, le GC va découvrir cela et le mettre sur le finaliseur file d'attente. handle est toujours égale à zéro, de sorte que le finaliseur ne fait rien. Notez que l'outil de finalisation est nécessaire pour être robuste face à un objet qui est en cours de finalisation, dont le constructeur n'a jamais été terminée. Vous êtes requis d'écrire les finaliseurs qui sont de cette forte. C'est encore une autre raison pourquoi vous devriez laisser par écrit les finaliseurs d'experts et de ne pas essayer de le faire vous-même.

Supposons Blah3() lancers. Le jet qui se passe après que la ressource est allouée. Mais encore une fois, foo n'est jamais rempli, nous n'avons jamais entrer dans l' finally, et l'objet est nettoyé par le finaliseur fil. Cette fois, la poignée est non nul, et le finaliseur il nettoie. Encore une fois, l'outil de finalisation est en cours d'exécution sur un objet dont le constructeur n'a jamais réussi, mais le finaliseur fonctionne de toute façon. Évidemment, il doit parce que cette fois, il avait du travail à faire.

Maintenant, supposons Blah2() lancers. Le lancer se fait après que la ressource est attribué, mais avant d' handle est rempli! Encore une fois, le finaliseur fonctionne, mais maintenant, handle est toujours de zéro et nous avons fuite de la poignée!

Vous avez besoin d'écrire extrêmement intelligente code pour empêcher cette fuite se produise. Maintenant, dans le cas de votre Font des ressources, qui est le diable s'en soucie? Nous avons fuite d'une poignée de police, une grosse affaire. Mais si vous avez absolument, positivement exiger que chaque ressource non managée être nettoyé quel que soit le calendrier des exceptions est alors vous avez un très difficile problème sur vos mains.

Le CLR a résoudre ce problème avec des serrures. Depuis C# 4, les verrous qui utilisent l' lock déclaration ont été mises en œuvre comme ceci:

bool lockEntered = false;
object lockObject = whatever;
try
{
    Monitor.Enter(lockObject, ref lockEntered);
    lock body here
}
finally
{
    if (lockEntered) Monitor.Exit(lockObject);
}

Enter a été très soigneusement écrite, de sorte que peu importe ce que les exceptions sont jetés, lockEntered est définie sur true si et seulement si la serrure a été effectivement prises. Si vous avez des exigences semblables ensuite ce que vous avez à faire est effectivement d'écrire:

    public Foo()
    {
        Blah1();
        AllocateResource(ref handle);
        Blah2();
        Blah3();
    }

et écrire AllocateResource intelligemment comme Monitor.Enter de sorte que peu importe ce qui se passe à l'intérieur d' AllocateResource, handle est rempli si et seulement si il a besoin d'être libéré.

Décrire les techniques pour le faire est au-delà de la portée de cette réponse. Consulter un spécialiste si vous avez cette exigence.

32voto

David Heffernan Points 292687

En complément de la réponse de @SLaks, voici l'IL pour votre code:

 .method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 74 (0x4a)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] class [System.Drawing]System.Drawing.Font font3,
        [1] class [System.Drawing]System.Drawing.Font font4,
        [2] bool CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldstr "Arial"
    IL_0006: ldc.r4 10
    IL_000b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
    IL_0010: stloc.0
    .try
    {
        IL_0011: ldstr "Arial"
        IL_0016: ldc.r4 10
        IL_001b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
        IL_0020: stloc.1
        .try
        {
            IL_0021: nop
            IL_0022: nop
            IL_0023: leave.s IL_0035
        } // end .try
        finally
        {
            IL_0025: ldloc.1
            IL_0026: ldnull
            IL_0027: ceq
            IL_0029: stloc.2
            IL_002a: ldloc.2
            IL_002b: brtrue.s IL_0034

            IL_002d: ldloc.1
            IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
            IL_0033: nop

            IL_0034: endfinally
        } // end handler

        IL_0035: nop
        IL_0036: leave.s IL_0048
    } // end .try
    finally
    {
        IL_0038: ldloc.0
        IL_0039: ldnull
        IL_003a: ceq
        IL_003c: stloc.2
        IL_003d: ldloc.2
        IL_003e: brtrue.s IL_0047

        IL_0040: ldloc.0
        IL_0041: callvirt instance void [mscorlib]System.IDisposable::Dispose()
        IL_0046: nop

        IL_0047: endfinally
    } // end handler

    IL_0048: nop
    IL_0049: ret
} // end of method Program::Main
 

Notez les blocs try / finally imbriqués.

17voto

Tim Long Points 4435

Ce code (basé sur l'échantillon original):

using System.Drawing;

public class Class1
{
    public Class1()
    {
        using (Font font3 = new Font("Arial", 10.0f),
                    font4 = new Font("Arial", 10.0f))
        {
            // Use font3 and font4.
        }
    }
}

Ce produit est le suivant CIL (dans Visual Studio 2013, le ciblage .NET 4.5.1):

.method public hidebysig specialname rtspecialname
        instance void  .ctor() cil managed
{
    // Code size       82 (0x52)
    .maxstack  2
    .locals init ([0] class [System.Drawing]System.Drawing.Font font3,
                  [1] class [System.Drawing]System.Drawing.Font font4,
                  [2] bool CS$4$0000)
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  nop
    IL_0008:  ldstr      "Arial"
    IL_000d:  ldc.r4     10.
    IL_0012:  newobj     instance void [System.Drawing]System.Drawing.Font::.ctor(string,
                                                                                  float32)
    IL_0017:  stloc.0
    .try
    {
        IL_0018:  ldstr      "Arial"
        IL_001d:  ldc.r4     10.
        IL_0022:  newobj     instance void [System.Drawing]System.Drawing.Font::.ctor(string,
                                                                                      float32)
        IL_0027:  stloc.1
        .try
        {
            IL_0028:  nop
            IL_0029:  nop
            IL_002a:  leave.s    IL_003c
        }  // end .try
        finally
        {
            IL_002c:  ldloc.1
            IL_002d:  ldnull
            IL_002e:  ceq
            IL_0030:  stloc.2
            IL_0031:  ldloc.2
            IL_0032:  brtrue.s   IL_003b
            IL_0034:  ldloc.1
            IL_0035:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
            IL_003a:  nop
            IL_003b:  endfinally
        }  // end handler
        IL_003c:  nop
        IL_003d:  leave.s    IL_004f
    }  // end .try
    finally
    {
        IL_003f:  ldloc.0
        IL_0040:  ldnull
        IL_0041:  ceq
        IL_0043:  stloc.2
        IL_0044:  ldloc.2
        IL_0045:  brtrue.s   IL_004e
        IL_0047:  ldloc.0
        IL_0048:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
        IL_004d:  nop
        IL_004e:  endfinally
    }  // end handler
    IL_004f:  nop
    IL_0050:  nop
    IL_0051:  ret
} // end of method Class1::.ctor

Comme vous pouvez le voir, l' try {} bloc ne commence qu'après la première affectation, qui se déroule à l' IL_0012. À première vue, cela ne semble allouer le premier élément non protégée dans le code. Toutefois, notez que le résultat est stocké dans l'emplacement 0. Si la deuxième allocation échoue, l' externe finally {} bloc s'exécute, et ceci récupère l'objet à partir de la position 0, c'est à dire la première affectation de font3, et appelle de ses Dispose() méthode.

Fait intéressant, la décompilation cette assemblée dotPeek produit est le suivant reconstitué source:

using System.Drawing;

public class Class1
{
    public Class1()
    {
        using (new Font("Arial", 10f))
        {
            using (new Font("Arial", 10f))
                ;
        }
    }
}

Le code décompilé confirme que tout est correct et que l' using est essentiellement développée en imbriquée usings. Le code CIL est un peu déroutant à regarder, et j'ai dû regarder cela pendant quelques minutes avant que j'ai bien compris ce qui se passait, donc je ne suis pas surpris de constater que certains "vieux contes de bonnes femmes" ont commencé à germer à ce sujet. Toutefois, le code généré est l'insaisissable vérité.

7voto

wdosanjos Points 3601

Voici un exemple de code pour prouver @SLaks réponse:

void Main()
{
    try
    {
        using (TestUsing t1 = new TestUsing("t1"), t2 = new TestUsing("t2"))
        {
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine("catch");
    }
    finally
    {
        Console.WriteLine("done");
    }

    /* outputs

        Construct: t1
        Construct: t2
        Dispose: t1
        catch
        done

    */
}

public class TestUsing : IDisposable
{
    public string Name {get; set;}

    public TestUsing(string name)
    {
        Name = name;

        Console.WriteLine("Construct: " + Name);

        if (Name == "t2") throw new Exception();
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose: " + Name);
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X