2017-12-28 17 views
0

DisposableDbObjectIDisposableの実装)を使用するクラスMyServiceを含むライブラリを作成しています。私はAutofac ContainerBuilder拡張を公開しています(オブジェクトの作成には非常にコストがかかります)。問題は、しばらくの間、DisposableDbObjectインスタンスをリフレッシュする必要があることです(ファイルから新しいバージョンのデータベースをロードする必要のあるメモリ内のDBのラッパーです)。私が知る限り、SingletonInstance(そしてContainerBuilder.Updateは廃止された)コンポーネントの参照を置き換える安全な方法はないので、私はDisposableDbObjectDisposableDbObjectProviderクラスにラップしてシングルトンとして登録し、何でも更新することができます下に横たわっている。だから私の設定はこれのように行く。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>(); 
    } 
} 

この設定には少なくとも3つの問題があります。

  1. (ASP.NET WebApi2など)、マルチスレッドクライアントアプリはMyServiceと1つのスレッドを登録する(これはASP.NETリクエストハンドラである)スレッドがある間に更新が行われた場合には、オブジェクトの2つの異なるバージョンにアクセスすることができますDisposableDbObject参照を交換した後は(私の非常に特定の場合には、これは、私はそれを避けることを好むだろうけれどもイベント十分かもしれません)

  2. を実行し、古いものはそれにDisposeと呼ばれている必要があります。今度はN> = 1のスレッドがそのオブジェクトへの参照を保持し、DisposeDisposableDbObjectProviderに呼び出している間に、それらのスレッドがObjectDisposedExceptionで終了することがあります(多くの場合)。

  3. クライアントが使用するオブジェクトの廃棄に責任を負うべきルールが破られます。私が考えていた

一つのアプローチは、IsAlive経由(WeakReference追跡リストとして古い参照保存DisposableDbObjectstaticなどのフィールドで、各アップデートに過渡、ガベージコレクトされている参照のためにそれをスキャンするDisposableDbObjectProviderの登録を変更することです(もしDisposableDbObjectProvider.Dispose意志behaを言うことができない性質)など

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(); 
     } 
    } 
} 

の下に、それらの上Disposeを呼び出すしかし、それだけで問題に何の2を解決していない可能性があり、まだ私はこのsoultionについて非常に安全に感じることはありませんいくつかのスレッドで同時に呼び出されています。

これらの問題を解決するにはどうすればよいでしょうか?もちろん、シングルトン登録の問題を回避するための私の解決策には欠陥があるかもしれません。もっと良いアプローチがあれば、それについて聞いてみたいと思います。

+0

DisposableDbObjectの更新時期はどのように決定されますか?パターンがある場合は、シングルトンとしての登録をやめ、適切な数のインスタンスを渡すことができます。 –

+0

'DisposableDbObject'のソースコードを表示してください。 _そうでなければ明白でない機会を開くかもしれません。 – mjwills

答えて

1

まず第一に、スレッドの安全性が欠けていると思います。オブジェクトが使用されている場合、他のすべてのスレッドが作業を完了するまで、オブジェクトを更新することはできません。更新ルーチンと同じです。それが行われる前に、他のスレッドはオブジェクトまたはそのメソッドにアクセスできないはずです。それは自明ではなく、深い理解なしにそのようなことを実装しようとするのを避けるだろう。うれしいことに、複数のスレッドを読み取ることができるように設計されているが、一度に1つのスレッドしか書き込むことができないように設計されたReaderWriterLockSlim classがあります。また、読み取りロックが取得されていないときのように既存のオブジェクトを破棄することができます。逆も同様です。

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(); 
    } 
} 

それはあなたのために働くかもしれないが、おそらくあなたが道に沿っていくつかの調整を行う必要がありますが、アイデアがうまくいけばはっきりと便利です。

関連する問題