2017-12-28 6 views
0

Ich erstelle eine Bibliothek, die eine Klasse MyService enthält, die ein Objekt 3rd Party DisposableDbObject (Implementierung IDisposable) verwendet. Ich exponieren Autofac ContainerBuilder Erweiterung, die es als eine einzelne Instanz registriert (Erstellung von Objekt ist sehr teuer). Die Sache ist, dass hin und wieder die DisposableDbObject Instanz aktualisiert werden muss (es ist ein Wrapper um einige In-Memory-DB, die eine neue Version der Datenbank aus der Datei laden muss). Da, soweit ich weiß, gibt es keine sichere Möglichkeit, die Referenz einer Komponente von SingletonInstance (und ContainerBuilder.Update ist veraltet) zu ersetzen Ich wickelte meine DisposableDbObject mit DisposableDbObjectProvider Klasse und registrieren Sie es als ein Singleton, mit einer freien Hand zu aktualisieren, was auch immer liegt darunter. So läuft mein Setup so.Ersetzen von als Singleton registriertem Einwegobjekt in Autofac

// DisposableDbObjectProvider.cs 
public interface IDisposableDbObjectProvider 
{ 
    DisposableDbObject GetDb(); 
} 

public class DisposableDbObjectProvider : IDisposableDbObjectProvider 
{ 
    private DisposableDbObject _obj; 
    public DisposableDbObjectProvider() 
    { 
     _obj = new DisposableDbObject("D:\\path\to\file"); 
    } 
    public DisposableDbObject GetDb() 
    { 
     return _obj; 
    } 
    public void UpdateDb() 
    { 
     _obj = new DisposableDbObject("D:\\path\to\new\file"); 
    } 
} 

// MyService.cs 
interface IMyService 
{ 
    string GetStuffFromDb(); 
} 

class MyService 
{ 
    private DisposableDbObjectProvider _provider; 

    class MyService(IDisposableDbObjectProvider provider) 
    { 
     _provider = provider; 
    } 

    public string GetStuffFromDb() 
    { 
     return _provider.GetDb().Read(...); 
    } 
} 


// AutofacExtensions.cs 
static class AutofacExtensions 
{ 
    public static ContainerBuilder WithMyService(this ContainerBuilder builder) 
    { 
      builder.RegisterType<DisposableDbObjectProvider >().As<IDisposableDbObjectProvider>().SingleInstance(); 
      builder.RegisterType<MyService>().As<IMyService>(); 
    } 
} 

Jetzt gibt es mindestens drei Probleme mit diesem Setup.

  1. die multithreading-fähige Client-Anwendung (wie ASP.NET WebApi2) registriert MyService und ein ein Gewinde (sei es ASP.NET Request-Handler) es zwei verschiedene Versionen des Objekts zugreifen kann, wenn die Aktualisierung durchgeführt wurde, während Threads Laufen (in meinem ganz speziellen Fall könnte das gut genug sein, Event obwohl ich es lieber vermeiden würde)

  2. Nach dem Ersetzen DisposableDbObject Referenz, die alte muss Dispose aufgerufen haben. Jetzt gibt es möglicherweise N> = 1 Threads, die Verweis auf das Objekt behält, und während ich Dispose in DisposableDbObjectProvider aufrufen kann diese Thread (und in vielen Fällen wird) am Ende mit ObjectDisposedException enden.

  3. Es bricht die Regel, dass der Client verantwortlich sein sollte für die Entsorgung des Objekts, das es verwendet.

Ein Ansatz, den ich darüber nachdachte, ist die Registrierung von DisposableDbObjectProvider sie ändern mit DisposableDbObject als static Feld und bei jeder Aktualisierung speichert die alte Referenz als WeakReference Tracking-Liste und scannen sie nach Referenzen auf transiente, den Müll gesammelt werden (via IsAlive Eigentum) und Dispose auf diejenigen nennen, wie unter

public class DisposableDbObjectProvider : IDisposableDbObjectProvider, IDisposable 
{ 
    private static DisposableDbObject _obj = new DisposableDbObject("D:\\path\to\file"); 
    private static List<WeakReference> _oldRefs; 
    public DisposableDbObject GetDb() 
    { 
     return _obj; 
    } 
    public void UpdateDb() 
    { 
     _oldRefs.Add(_obj); 
     _obj = new DisposableDbObject("D:\\path\to\new\file"); 
    } 

    public void Dispose() 
    { 
     var deadRefs = _oldRefs.Where(x => !x.IsAlive); 
     oldRefs = oldRefs.Exclude(deadRefs); 
     foreach(var deadRef in deadRefs) 
     { 
      ((IDisposable) deadRef.Target).Dispose(); 
     } 
    } 
} 

Aber das nur vielleicht nicht 2 lösen Problem und ich über diese soultion sehr sicher fühlen, immer noch nicht (kann nicht sagen, ob DisposableDbObjectProvider.Dispose wird Beha wie beabsichtigt, während gleichzeitig mehrere Threads aufgerufen werden.

Was wäre der beste Weg, um diese Probleme zu überwinden? Natürlich könnte meine Lösung, das Singleton-Registrierungsproblem zu umgehen, fehlerhaft sein, wenn es einen besseren Ansatz gibt, bin ich gespannt darauf, davon zu hören.

+0

Wie entscheidet das Programm, wann das DisposableDbObject aktualisiert werden soll? Wenn es ein Muster gibt, könnten Sie aufhören, es als Singleton zu registrieren und die richtige (Anzahl von) Instanzen herumreichen. –

+0

Bitte zeigen Sie uns den Quellcode für 'DisposableDbObject' an. _Sieht man das, können sich Möglichkeiten eröffnen, die sonst nicht offensichtlich sind. – mjwills

Antwort

1

Zuerst würde ich sagen, dass mir die Threadsicherheit fehlt. Wenn ein Objekt verwendet wird, kann es erst aktualisiert werden, wenn alle anderen Threads mit der Arbeit fertig sind. Gleiches gilt für die Update-Routine. Bevor es fertig ist, sollten andere Threads nicht auf das Objekt oder seine Methoden zugreifen können. Es ist nicht trivial und ich werde es vermeiden zu versuchen, so etwas ohne tiefes Verständnis zu implementieren. Gute Nachrichten gibt es eine ReaderWriterLockSlim class, die entwickelt wurde, um mehrere Threads zu lesen, aber nur einen einzigen Thread zum Schreiben zu ermöglichen. Sie können auch vorhandene Objekte löschen, da während des Schreibvorgangs keine Lesesperre erfasst wird und umgekehrt.

public class DisposableDbObjectProvider : IDisposableDbObjectProvider, IDisposable 
{ 
    private DisposableDbObject _obj = new DisposableDbObject("D:\\path\to\file"); 
    private ReaderWriteLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); 

    public DisposableDbObject AquireDb() 
    { 
     if(_lock.TryEnterReaderLock(100)) // how long to wait until entering fails 
     { 
      return _obj; 
     } 
     else 
     { 
      // unable to enter read lock in timeout 
      // do something 
     } 
    } 

    public void ReleaseDb() 
    { 
     // we need to exit lock after we are done with reading 
     _lock.ExitReadLock(); 
    } 

    public void UpdateDb() 
    { 
     if(_lock.TryEnterWriteLock(500)) // how long to wait until entering fails 
     { 
      _obj.Dispose(); 
      _obj = new DisposableDbObject("D:\\path\to\new\file"); 
      _lock.ExitWriteLock(); // We need to leave write lock to let read lock to be acquired 
     } 
     else 
     { 
      // unable to enter write lock in timeout 
      // do something 
     } 
    } 

    public void Dispose() 
    { 
     _obj.Dispose(); 
    } 
} 

Es kann für Sie arbeiten, aber wahrscheinlich müssen Sie einige Verbesserungen auf dem Weg zu tun, aber die Idee ist hoffentlich klar und hilfreich.

Verwandte Themen