2010-07-17 5 views
3

J'ai une classe avec un champ de Dictionary<object, IntPtr>.Finalisation de la collection Dictionary <object, IntPtr>

J'allouent la mémoire dynamique, lorsque l'utilisateur appelle une méthode de ma classe:

IntPtr somePointer = Marshal.AllocHGlobal(/*some desired size*/); 

Alors je vais utiliser cette mémoire dans un autre thread. En fait, après avoir effectué un travail, ce thread libère la mémoire allouée via Marshal.FreeHGlobal et supprime la clé appropriée de la collection. Mais il y a une certaine probabilité que ce thread se plante, donc je pense à une finalisation correcte.

Comment puis-je finaliser toute cette collection (dans le cas où un thread s'est écrasé et que ma mémoire reste allouée)? Je souhaite changer IntPtr en SafeHandle. Est-ce que cela aidera?

+0

La meilleure solution consiste à ne pas allouer de mémoire non managée en premier lieu. C'est seulement là pour certains scénarios p/Invoke, et p/Invoke n'est pas mentionné dans la description de votre problème.Puisque vous allouez et libérez la mémoire, il semble peu probable que vous ayez besoin de le faire. –

+0

Mon nouveau thread juste en passant ce slot alloué de la mémoire à exporter de la fonction kernel32.dll. Quand cette fonction se termine, je vais libérer cette fente. –

Répondre

1

Ecrire une enveloppe pour le IntPtr pourrait certainement travailler. Mais il n'est pas nécessaire que la mémoire reste valide tant que l'élément est stocké dans le dictionnaire. Si c'est le cas, le code client est beaucoup plus simple si vous créez votre propre répertoire, qui libère automatiquement la mémoire lorsqu'un élément est supprimé du dictionnaire. Pas besoin pour le client d'appeler Dispose() pour chaque item, c'est toujours un avantage.

Pour cela, dérivez votre propre classe de IDictionary<object, IntPtr> et IDisposable. Vous pouvez simplement déléguer la plupart des appels de méthode à un dictionnaire privé. Vous aurez besoin d'un Add() personnalisé pour allouer la mémoire, Delete pour le libérer. Et implémentez Dispose() et le finaliseur pour nettoyer. Le code est un peu laid si:

class MyDictionary : IDictionary<object, IntPtr>, IDisposable { 
    private Dictionary<object, IntPtr> impl = new Dictionary<object, IntPtr>(); 

    public void Add(object key) { 
     IntPtr mem = Marshal.AllocCoTaskMem(666); // Something smarter here... 
     impl.Add(key, mem); 
    } 
    public bool Remove(object key) { 
     if (!impl.ContainsKey(key)) return false; 
     Marshal.FreeCoTaskMem(impl[key]); 
     return impl.Remove(key); 
    } 
    protected void Dispose(bool disposing) { 
     foreach (IntPtr mem in impl.Values) Marshal.FreeCoTaskMem(mem); 
     if (disposing) impl.Clear(); 
    } 
    public void Dispose() { 
     Dispose(true); 
    } 
    ~MyDictionary() { 
     Dispose(false); 
    } 

    // Boilerplate 
    public void Add(object key, IntPtr value) { throw new NotImplementedException(); } 
    public void Add(KeyValuePair<object, IntPtr> item) { throw new NotImplementedException(); } 
    public bool Remove(KeyValuePair<object, IntPtr> item) { throw new NotImplementedException(); } 
    public bool ContainsKey(object key) { return impl.ContainsKey(key); } 
    public ICollection<object> Keys { get { return impl.Keys; }} 
    public bool TryGetValue(object key, out IntPtr value) { return impl.TryGetValue(key, out value); } 
    public ICollection<IntPtr> Values { get {return impl.Values; }} 
    public IntPtr this[object key] { get { return impl[key]; } set { impl[key] = value; } } 
    public void Clear() { impl.Clear(); } 
    public bool Contains(KeyValuePair<object, IntPtr> item) { return impl.Contains(item); } 
    public void CopyTo(KeyValuePair<object, IntPtr>[] array, int arrayIndex) { (impl as ICollection<KeyValuePair<object, IntPtr>>).CopyTo(array, arrayIndex); } 
    public int Count { get { return impl.Count; }} 
    public bool IsReadOnly { get { return (impl as ICollection<KeyValuePair<object, IntPtr>>).IsReadOnly; } } 
    public IEnumerator<KeyValuePair<object, IntPtr>> GetEnumerator() { return impl.GetEnumerator(); } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (impl as System.Collections.IEnumerable).GetEnumerator(); } 
} 

AllocCoTaskMem est le meilleur allocateur BTW, il n'a pas le bagage hérité.

+0

Merci pour la réponse. Est-ce vraiment sûr d'accéder à impl.Values ​​dans le finaliseur? Je suis un peu hésitant à ce sujet ... –

+1

Accéder aux membres qui ont un finaliseur est dangereux. Dictionary <> n'en a pas. –

+0

Dans ce cas, je pourrais simplement passer en revue toutes les clés de dictionnaire dans un finaliseur de ma classe et relacher la mémoire allouée restante. Mais je n'ai vraiment pas entendu dire que l'accès à des éléments tels que Dictionary soit sûr dans les finalizers. Par conséquent demandant –

1

Une option serait de mettre votre code thread dans un bloc try/finally et d'avoir enfin bloquer la mémoire, de cette façon si le thread plante (je suppose que vous voulez dire une exception?), Le bloc finally assurera la mémoire est libérée. Un SafeHandle personnalisé vous aidera si vous libérez la mémoire dans la méthode Dispose, mais pour que cela fonctionne, vous devrez avoir supprimé l'élément du dictionnaire avant que le thread ne se bloque, afin qu'il n'y ait plus de référence à la SafeHandle via le dictionnaire. Sinon, la mémoire n'est libérée qu'une fois le dictionnaire publié, ce qui dépend de la gestion de votre vie du dictionnaire.

Vous pouvez également envisager une solution plus exotique en utilisant un WeakReference, mais si vous le pouvez, je suggère le SafeHandle conjointement avec try/finally.

+0

C'est un bon point, mais: ma classe effectue l'allocation de mémoire dans le thread principal (le thread où l'utilisateur appelle la méthode exposée). Try-catch Mentionné devrait être dans un autre thread. Il y a une certaine probabilité (en réalité très faible) qu'un nouveau thread ne démarre jamais. C'est pourquoi je pense à la finalisation. –

+0

@ xenn_33, si le thread ne démarre jamais, il peut être préférable d'avoir un thread d'arrière-plan qui promène le dictionnaire et libère toutes les allocations "orphelines", donc si vous avez alloué de la mémoire pour un thread quantité de temps, ou quelque chose de similaire. Allouez-vous la mémoire et passez-la au nouveau thread? Si c'est le cas, pourquoi le nouveau thread demande-t-il la mémoire de la façon dont vous savez que le thread a démarré et que le bloc finally dans le thread peut prendre en charge la responsabilité de libérer la mémoire demandée. –

+0

Yup, vous avez raison sur le bon endroit pour l'allocation. Mais je m'inquiétais au sujet des finaliseurs uniquement dans le cas où mon nouveau thread engendré serait écrasé en raison de OutOfMemory, ThreadAbort et d'autres exceptions. –

1

Je pense que vous trouverez SafeHandle juste appelle CloseHandle lors du nettoyage, ce que je ne pense pas est la même chose que FreeHGlobal.

Vous seriez probablement mieux d'ajouter le modèle complet Dispose/Finalizer à votre classe qui contient la collection, et de le parcourir à travers le dictionnaire et le nettoyage. Ou écrire un wrapper avec un finaliseur pour le HGlobal.

Mise à jour: Cela peut être utile - http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/f74b7c3c-12c4-466b-9754-82e9dea8b83e

+0

Merci pour la réponse. >> Vous feriez probablement mieux d'ajouter le modèle complet Dispose/Finalizer à votre classe qui contient la collection, et de le faire parcourir le dictionnaire et le nettoyage. Mon inclination est que nous ne pouvons pas accéder à la ressource gérée (Dictionnaire) dans le finaliseur (au moins c'est une pratique extrêmement mauvaise). Ai-je tort? –

+0

Will, le lien est vraiment génial, merci beaucoup :) –

+0

Vous êtes nouveau ici, mais c'est la chose faite pour voter des choses que vous aimez! Content que tu l'aimes quand même. Je ne pense pas qu'il y ait un problème d'énumération d'un dictionnaire dans un finaliseur parce que le dictionnaire lui-même ne sera pas finalisé lui-même, mais de toute façon, un emballage sécurisé sur la poignée elle-même évite la question et se sent plus propre. –

Questions connexes