C # Nesne Birleştirme Deseni uygulaması


165

Herkes Sql bağlantı havuzu damar sınırlı bir kaynak için paylaşılan bir nesne havuzu stratejisi uygulamak iyi bir kaynak var mı? (yani tamamen iş parçacığı güvenli olduğu için uygulanacaktır).

@Aaronaught'ın açıklama talebini takip etmek için havuz kullanımı, harici bir servise yük dengeleme talepleri için olacaktır. Doğrudan durumumun aksine, hemen anlaşılması daha kolay bir senaryoya koymak. ISessionNHibernate nesnesine benzer şekilde çalışan bir oturum nesnesi var . Her benzersiz oturumun veritabanıyla bağlantısını yönetmesi. Şu anda 1 uzun çalışan oturum nesnesi var ve servis sağlayıcım bu bireysel oturum benim kullanımını sınırlamak nerede sorunları ile karşılaşıyorum.

Tek bir oturumun uzun süredir devam eden bir hizmet hesabı olarak ele alınacağına dair beklentilerinin olmayışı nedeniyle, görünüşe göre, hizmetlerini bozan bir müşteri olarak ele alıyorlar. Bu da beni burada soruma getiriyor, 1 bireysel oturum yerine, farklı oturumlardan oluşan bir havuz oluşturuyor ve daha önce yaptığım gibi tek bir odak noktası oluşturmak yerine, istekleri bu çoklu oturumlar boyunca hizmete bölerdim.

Umarım bu arka plan bazı sorularınızı doğrudan cevaplamaktan başka bir değer sunar:

S: Nesnelerin oluşturulması pahalı mı?
C: Hiçbir nesne sınırlı kaynak havuzu değildir

S: Çok sık edinilecek / serbest bırakılacaklar mı?
C: Evet, bir kez daha, her bir tek sayfa isteği süresince 1'in genellikle edinildiği ve serbest bırakıldığı NHibernate ISessions düşünülebilir.

S: Basit bir ilk gelen ilk hizmet yeterli olacak mı yoksa daha akıllı bir şeye mi ihtiyacınız var, yani bu açlığı önleyecek mi?
C: Basit bir yuvarlak robin tipi dağıtım yeterli olacaktır, açlıktan ötürü, arayanların serbest bırakılmayı beklerken engellenecek mevcut oturumları yoksa demek istiyorum. Oturumlar farklı arayanlar tarafından paylaşılabildiğinden bu gerçekten geçerli değildir. Amacım, kullanımı tek bir oturum yerine birden çok oturuma dağıtmak.

Bunun muhtemelen bir nesne havuzunun normal kullanımından bir sapma olduğuna inanıyorum, bu yüzden aslında bu kısmı dışarıda bıraktım ve sadece açlık durumunun oluşmasına izin vermek yerine nesnelerin paylaşılmasına izin vermek için deseni uyarlamayı planladım.

S: Öncelikler, tembel ve istekli yükleme vb.
YANIT: Öncelik yoktur, basitlik uğruna sadece havuzun kendisinin yaratılmasında mevcut nesnelerin havuzunu oluşturacağımı varsayalım.


1
Bize gereksinimlerinizden biraz bahsedebilir misiniz? Tüm havuzlar eşit yaratılmaz. Nesnelerin oluşturulması pahalı mı? Çok sık edinilecek / serbest bırakılacaklar mı? Basit bir ilk gelen ilk hizmet yeterli olacak mı yoksa daha akıllı bir şeye mi ihtiyacınız var, yani açlığı önleyecek mi? Öncelikler, tembel ve istekli yükleme vb. Ekleyebileceğiniz her şey bize (veya en azından bana) daha kapsamlı bir cevap bulmamıza yardımcı olacaktır.
Aaronaught

Chris - sadece 2. ve 3. paragraflarınıza bakın ve bu oturumların gerçekten süresiz olarak devam etmesi gerekip gerekmediğini mi merak ediyorsunuz? Servis sağlayıcınızın beğenmediği anlaşılıyor (uzun süren oturumlar) ve bu nedenle yeni oturumları gerektiği gibi döndüren ve kullanılmadığında (belirli bir süre sonra) kapatan bir havuz uygulaması arıyor olabilirsiniz. . Bu yapılabilir, ancak biraz daha karmaşıktır, bu yüzden onaylamak istiyorum.
Aaronaught

Bu sağlam bir çözüme ihtiyacım olup olmadığından emin değilim ama henüz çözümüm sadece varsayımsal. Servis sağlayıcımın bana yalan söylemesi ve servislerinin aşırı satılması ve sadece kullanıcıyı suçlamanın bir yolu için bir bahane bulması mümkündür.
Chris Marisic

1
Ben TPL DataFlow BufferBlock ihtiyacınız olan çoğu yapıyor düşünüyorum.
harcı

1
Dişli ortamlarda havuz oluşturma, Kaynak Havuzu ve Kaynak Önbelleği gibi tasarım desenleriyle çözülen tekrarlayan bir sorundur. Daha fazla bilgi için Kalıp Odaklı Yazılım Mimarisi, Cilt 3: Kaynak Yönetimi Kalıpları'na göz atın .
Fuhrmanator

Yanıtlar:


59

.NET Core'da Nesne Birleştirme

Dotnet çekirdek temel sınıf kütüphanesi (BCL) ilave bir amacı havuzu bir uygulama vardır. Özgün GitHub sorununu buradan okuyabilir ve System.Buffers kodunu görüntüleyebilirsiniz . Şu anda ArrayPoolkullanılabilir olan tek tiptir ve dizileri havuzlamak için kullanılır. Burada güzel bir blog yazısı var .

namespace System.Buffers
{
    public abstract class ArrayPool<T>
    {
        public static ArrayPool<T> Shared { get; internal set; }

        public static ArrayPool<T> Create(int maxBufferSize = <number>, int numberOfBuffers = <number>);

        public T[] Rent(int size);

        public T[] Enlarge(T[] buffer, int newSize, bool clearBuffer = false);

        public void Return(T[] buffer, bool clearBuffer = false);
    }
}

Kullanımının bir örneği ASP.NET Core'da görülebilir. Dotnet çekirdeği BCL'sinde olduğu için, ASP.NET Core nesne havuzunu Newtonsoft.Json'un JSON serileştiricisi gibi diğer nesnelerle paylaşabilir. Newtonsoft.Json'un bunu nasıl yaptığı hakkında daha fazla bilgi için bu blog gönderisini okuyabilirsiniz .

Microsoft Roslyn C # Derleyicisinde Nesne Birleştirme

Yeni Microsoft Roslyn C # derleyicisi, normalde yeni alınan ve çok sık toplanan çöpleri toplayan sık kullanılan nesneleri havuzlamak için kullanılan ObjectPool türünü içerir . Bu, gerçekleşmesi gereken çöp toplama işlemlerinin miktarını ve boyutunu azaltır. Tümü ObjectPool kullanan birkaç farklı alt uygulama vardır (Bkz: Neden Roslyn'de Nesne Havuzlaması bu kadar çok uygulama var? ).

1 - SharedPools - BigDefault kullanılıyorsa 20 veya 100 nesneden oluşan bir havuz saklar.

// Example 1 - In a using statement, so the object gets freed at the end.
using (PooledObject<Foo> pooledObject = SharedPools.Default<List<Foo>>().GetPooledObject())
{
    // Do something with pooledObject.Object
}

// Example 2 - No using statement so you need to be sure no exceptions are not thrown.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
// Do something with list
SharedPools.Default<List<Foo>>().Free(list);

// Example 3 - I have also seen this variation of the above pattern, which ends up the same as Example 1, except Example 1 seems to create a new instance of the IDisposable [PooledObject<T>][4] object. This is probably the preferred option if you want fewer GC's.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
try
{
    // Do something with list
}
finally
{
    SharedPools.Default<List<Foo>>().Free(list);
}

2 - ListPool ve StringBuilderPool - Kesinlikle ayrı uygulamalar değil, özellikle List ve StringBuilder's için yukarıda gösterilen SharedPools uygulaması etrafındaki sarmalayıcılar. Bu, SharedPools'ta depolanan nesne havuzunu yeniden kullanır.

// Example 1 - No using statement so you need to be sure no exceptions are thrown.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
// Do something with stringBuilder
StringBuilderPool.Free(stringBuilder);

// Example 2 - Safer version of Example 1.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
try
{
    // Do something with stringBuilder
}
finally
{
    StringBuilderPool.Free(stringBuilder);
}

3 - PooledDictionary ve PooledHashSet - Bunlar doğrudan ObjectPool kullanır ve tamamen ayrı bir nesne havuzuna sahiptir. 128 nesneden oluşan bir havuz saklar.

// Example 1
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
// Do something with hashSet.
hashSet.Free();

// Example 2 - Safer version of Example 1.
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
try
{
    // Do something with hashSet.
}
finally
{
    hashSet.Free();
}

Microsoft.IO.RecyclableMemoryStream

Bu kütüphane MemoryStreamnesneler için havuz oluşturur . Bunun yerine bir yedek System.IO.MemoryStream. Tam olarak aynı anlambilime sahiptir. Bing mühendisleri tarafından tasarlanmıştır. Blog gönderisini buradan okuyun veya GitHub'daki kodu görün .

var sourceBuffer = new byte[]{0,1,2,3,4,5,6,7}; 
var manager = new RecyclableMemoryStreamManager(); 
using (var stream = manager.GetStream()) 
{ 
    stream.Write(sourceBuffer, 0, sourceBuffer.Length); 
}

Not RecyclableMemoryStreamManagero tüm için yaşayacak ve bir kez beyan edilmelidir süreç-bu havuzudur. İsterseniz birden fazla havuz kullanmak mükemmeldir.


2
Bu harika bir cevap. C # 6 ve VS2015 RTM olduktan sonra, muhtemelen kabul edilen cevap yapacağız, çünkü Rosyln'in kendisi tarafından kullanılan şekilde ayarlanmışsa açıkça en iyisi.
Chris Marisic

Kabul ediyorum, ancak hangi uygulamayı kullanırdınız? Roslyn üç tane içeriyor. Cevabımda sorumun bağlantısına bakın.
Muhammad Rehan Saeed

1
Her biri çok açık bir şekilde tanımlanmış amaçlara sahip gibi görünüyor, sadece açılmış uçlu bir beden seçiminden çok daha iyi .
Chris Marisic

1
@MuhammadRehanSairePool ile harika bir eklenti yaptı
Chris Marisic

1
Bunun RecyclableMemoryStreamultra yüksek performans optimizasyonları için inanılmaz bir ek olduğunu görmek .
Chris Marisic

315

Bu soru, birkaç bilinmeyen nedeniyle beklenenden biraz daha zor: Toplanan kaynağın davranışı, nesnelerin beklenen / gerekli ömrü, havuzun gerekli olmasının gerçek nedeni, vb. Tipik olarak havuzlar özel amaçlıdır vb havuzları, bağlantı havuzları, - Eğer kaynak yapar tam olarak ne olduğunu ve daha da önemlisi var ne zaman birini optimize etmek kolaydır, çünkü kontrolünü bu kaynağın nasıl uygulandığını üzerinde.

Bu kadar basit olmadığından, yapmaya çalıştığım, deneyebileceğiniz ve neyin en iyi çalıştığını görebileceğiniz oldukça esnek bir yaklaşım sunmaktır. Uzun yazı için şimdiden özür dileriz, ancak genel amaçlı iyi bir kaynak havuzunun uygulanması söz konusu olduğunda kapsayacak çok şey vardır. ve gerçekten sadece yüzeyi çiziyorum.

Genel amaçlı bir havuzun birkaç ana "ayarına" sahip olması gerekir:

  • Kaynak yükleme stratejisi - istekli veya tembel;
  • Kaynak yükleme mekanizması - gerçekte nasıl inşa edileceği;
  • Erişim stratejisi - göründüğü kadar basit olmayan "yuvarlak robin" den bahsediyorsunuz; bu uygulama benzer , ancak mükemmel olmayan dairesel bir arabellek kullanabilir , çünkü havuzun kaynakların ne zaman geri kazanıldığı konusunda kontrolü yoktur. Diğer seçenekler FIFO ve LIFO; FIFO'nun rasgele erişimli bir modeli daha olacak, ancak LIFO, En Son Kullanılanlar için kullanılan bir serbest bırakma stratejisinin uygulanmasını önemli ölçüde kolaylaştırıyor (ki bunun kapsam dışında olduğunu söylediniz, ancak yine de bahsetmeye değer).

Kaynak yükleme mekanizması için .NET zaten temiz bir soyutlama sağlar - delegeler.

private Func<Pool<T>, T> factory;

Bunu havuzun yapıcısından geçirin ve işimiz bitti. new()Kısıtlamalı genel bir tür kullanmak da işe yarar, ancak bu daha esnektir.


Diğer iki parametreden, erişim stratejisi daha karmaşık bir canavardır, bu yüzden yaklaşımım kalıtım (arayüz) tabanlı bir yaklaşım kullanmaktı:

public class Pool<T> : IDisposable
{
    // Other code - we'll come back to this

    interface IItemStore
    {
        T Fetch();
        void Store(T item);
        int Count { get; }
    }
}

Buradaki kavram basittir - ortak Poolsınıfın iş parçacığı güvenliği gibi ortak sorunları ele almasına izin vereceğiz , ancak her erişim modeli için farklı bir "eşya deposu" kullanacağız. LIFO kolayca bir yığınla temsil edilir, FIFO bir kuyruktur ve List<T>bir yuvarlak erişim erişim modeline yaklaşmak için bir ve dizin işaretçisi kullanarak çok optimize edilmemiş ancak muhtemelen yeterli bir dairesel tampon uygulaması kullandım .

Aşağıdaki tüm sınıflar iç sınıflardır Pool<T>- bu bir stil seçimiydi, ancak bunlar gerçekten dışarıda kullanılmadığı Pooliçin en mantıklı.

    class QueueStore : Queue<T>, IItemStore
    {
        public QueueStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Dequeue();
        }

        public void Store(T item)
        {
            Enqueue(item);
        }
    }

    class StackStore : Stack<T>, IItemStore
    {
        public StackStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Pop();
        }

        public void Store(T item)
        {
            Push(item);
        }
    }

Bunlar bariz olanlardır - yığın ve kuyruk. Gerçekten çok fazla açıklama gerektirdiğini sanmıyorum. Dairesel tampon biraz daha karmaşıktır:

    class CircularStore : IItemStore
    {
        private List<Slot> slots;
        private int freeSlotCount;
        private int position = -1;

        public CircularStore(int capacity)
        {
            slots = new List<Slot>(capacity);
        }

        public T Fetch()
        {
            if (Count == 0)
                throw new InvalidOperationException("The buffer is empty.");

            int startPosition = position;
            do
            {
                Advance();
                Slot slot = slots[position];
                if (!slot.IsInUse)
                {
                    slot.IsInUse = true;
                    --freeSlotCount;
                    return slot.Item;
                }
            } while (startPosition != position);
            throw new InvalidOperationException("No free slots.");
        }

        public void Store(T item)
        {
            Slot slot = slots.Find(s => object.Equals(s.Item, item));
            if (slot == null)
            {
                slot = new Slot(item);
                slots.Add(slot);
            }
            slot.IsInUse = false;
            ++freeSlotCount;
        }

        public int Count
        {
            get { return freeSlotCount; }
        }

        private void Advance()
        {
            position = (position + 1) % slots.Count;
        }

        class Slot
        {
            public Slot(T item)
            {
                this.Item = item;
            }

            public T Item { get; private set; }
            public bool IsInUse { get; set; }
        }
    }

Bir dizi farklı yaklaşım seçebilirdim, ancak sonuçta kaynaklara yaratıldıkları sırayla erişilmesi gerekiyor, bu da onlara referansları sürdürmemiz, ancak bunları "kullanımda" olarak işaretlememiz gerektiği (veya ). En kötü senaryoda, yalnızca bir yuva kullanılabilir ve her getirme için arabelleğin tam bir yinelemesini alır. Yüzlerce kaynağınız toplanmışsa ve saniyede birkaç kez edinip serbest bırakırsanız bu kötüdür; 5-10 maddelik bir havuz için bir sorun değil ve kaynakların hafifçe kullanıldığı tipik durumda, sadece bir veya iki yuva ilerletmek zorunda.

Unutmayın, bu sınıflar özel iç sınıflardır - bu yüzden çok fazla hata kontrolüne ihtiyaç duymazlar, havuzun kendisi bunlara erişimi kısıtlar.

Bir numaralandırma ve bir fabrika yöntemiyle atın ve bu kısmı bitirdik:

// Outside the pool
public enum AccessMode { FIFO, LIFO, Circular };

    private IItemStore itemStore;

    // Inside the Pool
    private IItemStore CreateItemStore(AccessMode mode, int capacity)
    {
        switch (mode)
        {
            case AccessMode.FIFO:
                return new QueueStore(capacity);
            case AccessMode.LIFO:
                return new StackStore(capacity);
            default:
                Debug.Assert(mode == AccessMode.Circular,
                    "Invalid AccessMode in CreateItemStore");
                return new CircularStore(capacity);
        }
    }

Çözülmesi gereken bir sonraki sorun yükleme stratejisidir. Üç tür tanımladım:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

İlk ikisi kendi kendini açıklayıcı olmalıdır; üçüncüsü bir çeşit melezdir, kaynakları tembel yükler, ancak havuz dolana kadar herhangi bir kaynağı yeniden kullanmaya başlamaz. Havuzun tam olmasını istiyorsanız (ki bu sizin yaptığınız gibi geliyor) ama ilk erişime kadar onları oluşturma maliyetini ertelemek istiyorsanız (yani başlangıç ​​zamanlarını iyileştirmek) bu iyi bir değiş tokuş olacaktır.

Yükleme yöntemleri gerçekten çok karmaşık değil, şimdi eşya deposu soyutlamasına sahibiz:

    private int size;
    private int count;

    private T AcquireEager()
    {
        lock (itemStore)
        {
            return itemStore.Fetch();
        }
    }

    private T AcquireLazy()
    {
        lock (itemStore)
        {
            if (itemStore.Count > 0)
            {
                return itemStore.Fetch();
            }
        }
        Interlocked.Increment(ref count);
        return factory(this);
    }

    private T AcquireLazyExpanding()
    {
        bool shouldExpand = false;
        if (count < size)
        {
            int newCount = Interlocked.Increment(ref count);
            if (newCount <= size)
            {
                shouldExpand = true;
            }
            else
            {
                // Another thread took the last spot - use the store instead
                Interlocked.Decrement(ref count);
            }
        }
        if (shouldExpand)
        {
            return factory(this);
        }
        else
        {
            lock (itemStore)
            {
                return itemStore.Fetch();
            }
        }
    }

    private void PreloadItems()
    {
        for (int i = 0; i < size; i++)
        {
            T item = factory(this);
            itemStore.Store(item);
        }
        count = size;
    }

Yukarıdaki sizeve countalanları, havuzun maksimum boyutunu ve havuzun sahip olduğu toplam kaynak sayısını (ancak zorunlu olarak mevcut değildir ) ifade eder. AcquireEageren basit olanı, bir öğenin zaten mağazada olduğunu varsayar - bu öğeler inşaatta, yani en PreloadItemsson gösterilen yöntemde önceden yüklenir .

AcquireLazyhavuzda ücretsiz öğeler olup olmadığını kontrol eder ve yoksa yeni bir öğe oluşturur. AcquireLazyExpandinghavuz henüz hedef boyutuna ulaşmadığı sürece yeni bir kaynak oluşturur. Ben kilitleme aza indirmek için bu optimize etmek denedim ve ben hiç hata (ı yapmadınız umut var çok kanallı şartlarda bu test, ama besbelli değil etraflıca).

Bu yöntemlerin hiçbirinin mağazanın maksimum boyuta ulaşıp ulaşmadığını görmek için neden uğraşmadığını merak ediyor olabilirsiniz. Birazdan buna gireceğim.


Şimdi havuzun kendisi için. İşte bazıları zaten gösterilen özel verilerin tamamı:

    private bool isDisposed;
    private Func<Pool<T>, T> factory;
    private LoadingMode loadingMode;
    private IItemStore itemStore;
    private int size;
    private int count;
    private Semaphore sync;

Son paragrafta ele aldığım soruyu cevaplayarak - yaratılan toplam kaynak sayısını sınırlandırmayı nasıl garanti ederiz - .NET'in zaten bunun için mükemmel bir araca sahip olduğu ortaya çıkıyor, buna Semaphore deniyor ve özellikle bir sabitlemeye izin vermek için tasarlandı bir kaynağa erişim iş parçacığı sayısı (bu durumda "kaynak" iç öğe deposudur). Tam üretici / tüketici kuyruğu uygulamadığımızdan, bu ihtiyaçlarımız için mükemmel bir şekilde yeterlidir.

Yapıcı şöyle görünür:

    public Pool(int size, Func<Pool<T>, T> factory,
        LoadingMode loadingMode, AccessMode accessMode)
    {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", size,
                "Argument 'size' must be greater than zero.");
        if (factory == null)
            throw new ArgumentNullException("factory");

        this.size = size;
        this.factory = factory;
        sync = new Semaphore(size, size);
        this.loadingMode = loadingMode;
        this.itemStore = CreateItemStore(accessMode, size);
        if (loadingMode == LoadingMode.Eager)
        {
            PreloadItems();
        }
    }

Burada sürpriz olmamalı. Dikkat edilmesi gereken tek şey, PreloadItemsdaha önce gösterilen yöntemi kullanarak istekli yükleme için özel kasa .

Neredeyse her şey şimdiye kadar temiz bir şekilde soyutlandığından, gerçek Acquireve Releaseyöntemler gerçekten çok basittir:

    public T Acquire()
    {
        sync.WaitOne();
        switch (loadingMode)
        {
            case LoadingMode.Eager:
                return AcquireEager();
            case LoadingMode.Lazy:
                return AcquireLazy();
            default:
                Debug.Assert(loadingMode == LoadingMode.LazyExpanding,
                    "Unknown LoadingMode encountered in Acquire method.");
                return AcquireLazyExpanding();
        }
    }

    public void Release(T item)
    {
        lock (itemStore)
        {
            itemStore.Store(item);
        }
        sync.Release();
    }

Daha önce açıklandığı gibi, Semaphoreeşya mağazasının durumunu dini olarak kontrol etmek yerine eşzamanlılığı kontrol etmek için kullanıyoruz. Edinilen öğeler doğru bir şekilde serbest bırakıldığı sürece endişelenecek bir şey yok.

Son fakat en az değil, temizleme var:

    public void Dispose()
    {
        if (isDisposed)
        {
            return;
        }
        isDisposed = true;
        if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
        {
            lock (itemStore)
            {
                while (itemStore.Count > 0)
                {
                    IDisposable disposable = (IDisposable)itemStore.Fetch();
                    disposable.Dispose();
                }
            }
        }
        sync.Close();
    }

    public bool IsDisposed
    {
        get { return isDisposed; }
    }

Bu IsDisposedmülkün amacı bir anda netleşecek. DisposeGerçekten yaptığı tüm ana yöntem, eğer gerçek havuzlanmış öğeleri uygulamaktır IDisposable.


Şimdi temelde bunu bir try-finallyblokla olduğu gibi kullanabilirsiniz , ancak bu sözdiziminden hoşlanmıyorum, çünkü sınıflar ve yöntemler arasında havuzlanmış kaynaklardan geçmeye başlarsanız çok kafa karıştırıcı olacaktır. Bu bir kaynak kullandığı ana sınıfı bile etmediğini mümkündür sahip havuza bir başvuru. Gerçekten dağınık hale gelir, bu yüzden daha iyi bir yaklaşım "akıllı" bir havuzlu nesne yaratmaktır.

Diyelim ki şu basit arayüz / sınıfla başlıyoruz:

public interface IFoo : IDisposable
{
    void Test();
}

public class Foo : IFoo
{
    private static int count = 0;

    private int num;

    public Foo()
    {
        num = Interlocked.Increment(ref count);
    }

    public void Dispose()
    {
        Console.WriteLine("Goodbye from Foo #{0}", num);
    }

    public void Test()
    {
        Console.WriteLine("Hello from Foo #{0}", num);
    }
}

İşte benzersiz kimlikler üretmek için bazı Fookaynak IFookodu uygulayan ve sahip olan taklit tek kullanımlık kaynağımız . Yaptığımız başka bir özel, havuzlanmış nesne oluşturmak:

public class PooledFoo : IFoo
{
    private Foo internalFoo;
    private Pool<IFoo> pool;

    public PooledFoo(Pool<IFoo> pool)
    {
        if (pool == null)
            throw new ArgumentNullException("pool");

        this.pool = pool;
        this.internalFoo = new Foo();
    }

    public void Dispose()
    {
        if (pool.IsDisposed)
        {
            internalFoo.Dispose();
        }
        else
        {
            pool.Release(this);
        }
    }

    public void Test()
    {
        internalFoo.Test();
    }
}

Bu sadece tüm "gerçek" yöntemleri kendi içine yakınlaştırır IFoo(bunu Castle gibi bir Dinamik Proxy kütüphanesi ile yapabiliriz, ama buna girmeyeceğim). Ayrıca Pool, onu oluşturana bir referans tutar , böylece Disposebu nesneyi otomatik olarak havuza geri bırakır. Hariç havuz zaten elden çıkarılan kaldığında - biz "temizleme" modunda ve aslında bu durumda olan bu araçlar , iç kaynak temizler yerine.


Yukarıdaki yaklaşımı kullanarak, şöyle bir kod yazacağız:

// Create the pool early
Pool<IFoo> pool = new Pool<IFoo>(PoolSize, p => new PooledFoo(p),
    LoadingMode.Lazy, AccessMode.Circular);

// Sometime later on...
using (IFoo foo = pool.Acquire())
{
    foo.Test();
}

Bunu yapabilmek çok iyi bir şey. Bu kod anlamına gelir kullanırIFoo (bunu yaratan kod aksine) aslında havuz farkında olması gerekmez. En sevdiğiniz DI kitaplığını ve sağlayıcı / fabrika olarak kullanarak nesne enjekte edebilirsiniz .IFooPool<T>


Kopyalama ve yapıştırma keyfi için tam kodu PasteBin'e koydum . Ayrıca , farklı yükleme / erişim modları ve çok iş parçacıklı koşullarla oynamak, iş parçacığı açısından güvenli ve buggy olmadığından emin olmak için kullanabileceğiniz kısa bir test programı da vardır .

Bunlardan herhangi biriyle ilgili herhangi bir sorunuz veya endişeniz varsa bize bildirin.


62
SO hakkında okuduğum en eksiksiz, yararlı ve ilginç cevaplardan biri.
Josh Smeaton

Nesneleri serbest bırakmak her zaman çok sızdıran bir şekilde ele alındığı için özellikle PooledFoo kısmı için @Josh ile daha fazla anlaşamadım ve bunu kullanmayı mümkün kılmanın en mantıklı olacağını hayal etmiştim senin göstermiş olduğun gibi, sadece oturmadım ve inşa etmeye çalıştım, cevabın bana problemimi çözmek için ihtiyaç duyabileceğim tüm bilgileri verdiğinde. Özel durumum için bunu çoğunlukla basitleştirebileceğimi düşünüyorum, çünkü örnekleri iş parçacıkları arasında paylaşabileceğim ve onları havuza geri bırakmam gerekmiyor.
Chris Marisic

Ancak, basit yaklaşım ilk önce işe yaramazsa, davam için serbest bırakmayı akıllıca nasıl ele alabileceğim konusunda birkaç fikrim var. Özellikle oturumun kendisinin hatalı olduğunu belirleyebilmek ve atmak ve havuza yenisini koymak için serbest bırakmayı düşünüyorum. Ne olursa olsun bu yazı bu noktada hemen hemen C # 3.0 nesne havuzu kesin kılavuz, başka kimse bu konuda daha fazla yorum olup olmadığını görmek için sabırsızlanıyorum.
Chris Marisic

@Chris: WCF istemci proxy'lerinden bahsediyorsanız, bunun için bir desene sahibim, ancak etkili bir şekilde kullanmak için bir bağımlılık enjektörüne veya yöntem önleyicisine ihtiyacınız var. DI sürümü, hatalı yeni bir sürüm elde etmek için çekirdeği özel bir sağlayıcı ile kullanır, yöntem yakalama sürümü (benim tercihim) sadece mevcut bir proxy'yi sarar ve her birine bir hata denetimi ekler. Böyle bir havuza entegre etmek ne kadar kolay emin değilim (gerçekten yazdım çünkü gerçekten denemedim!) Ama kesinlikle mümkün olacaktır.
Aaronaught

5
Çok etkileyici, ancak çoğu durum için biraz fazla tasarlanmış. Böyle bir şeyin çerçevenin bir parçası olmasını beklerdim.
ChaosPandion

7

Böyle bir şey ihtiyaçlarınıza uygun olabilir.

/// <summary>
/// Represents a pool of objects with a size limit.
/// </summary>
/// <typeparam name="T">The type of object in the pool.</typeparam>
public sealed class ObjectPool<T> : IDisposable
    where T : new()
{
    private readonly int size;
    private readonly object locker;
    private readonly Queue<T> queue;
    private int count;


    /// <summary>
    /// Initializes a new instance of the ObjectPool class.
    /// </summary>
    /// <param name="size">The size of the object pool.</param>
    public ObjectPool(int size)
    {
        if (size <= 0)
        {
            const string message = "The size of the pool must be greater than zero.";
            throw new ArgumentOutOfRangeException("size", size, message);
        }

        this.size = size;
        locker = new object();
        queue = new Queue<T>();
    }


    /// <summary>
    /// Retrieves an item from the pool. 
    /// </summary>
    /// <returns>The item retrieved from the pool.</returns>
    public T Get()
    {
        lock (locker)
        {
            if (queue.Count > 0)
            {
                return queue.Dequeue();
            }

            count++;
            return new T();
        }
    }

    /// <summary>
    /// Places an item in the pool.
    /// </summary>
    /// <param name="item">The item to place to the pool.</param>
    public void Put(T item)
    {
        lock (locker)
        {
            if (count < size)
            {
                queue.Enqueue(item);
            }
            else
            {
                using (item as IDisposable)
                {
                    count--;
                }
            }
        }
    }

    /// <summary>
    /// Disposes of items in the pool that implement IDisposable.
    /// </summary>
    public void Dispose()
    {
        lock (locker)
        {
            count = 0;
            while (queue.Count > 0)
            {
                using (queue.Dequeue() as IDisposable)
                {

                }
            }
        }
    }
}

Örnek Kullanım

public class ThisObject
{
    private readonly ObjectPool<That> pool = new ObjectPool<That>(100);

    public void ThisMethod()
    {
        var that = pool.Get();

        try
        { 
            // Use that ....
        }
        finally
        {
            pool.Put(that);
        }
    }
}

1
Daha önceki yorumu çizin. Ben sadece garip buldum çünkü bu havuz herhangi bir eşik var gibi görünmüyor ve belki de gerek yok, bu gereksinimlere bağlıdır.
Aaronaught

1
@Aaronaught - Gerçekten bu kadar garip mi? Sadece gereken işlevselliği sunan hafif bir havuz oluşturmak istedim. Sınıfı doğru kullanmak istemciye kalmıştır.
ChaosPandion

1
+1, yalnızca destek türünü bir List / HashTable vb. Olarak değiştirip sayacı döndürmek için değiştirerek amacımlara uyarlanabilen çok basit bir çözüm için. Rastgele soru Havuz nesnesinin kendisinin yönetimini nasıl ele alırsınız? Sadece bir IOC konteynerine yapıştıracak mısın?
Chris Marisic

1
Bu salt okunur olmalı mı? Ama nihayet bir ifadenin içine koymanızın garip olduğunu görüyorum, eğer bir istisna varsa, kendi nesnesinin hatalı olması muhtemel olmaz mı? Bunu Putyöntemin içinde ele alır ve nesnenin hatalı olup olmadığını kontrol etmek ve bir öncekini eklemek yerine havuza eklenecek yeni bir örnek oluşturmak için basitliği dışarıda bırakır mısınız?
Chris Marisic

1
@Chris - Geçmişte yararlı bulduğum basit bir araç öneriyorum. Gerisi size kalmış. Uygun gördüğünüz gibi kodu değiştirin ve kullanın.
ChaosPandion


4

Gün içinde Microsoft, COM nesneleri için nesne havuzu oluşturmak için Microsoft Transaction Server (MTS) ve daha sonra COM + aracılığıyla bir çerçeve sağlamıştır. Bu işlevsellik, .NET Framework'te ve şimdi Windows Communication Foundation'da System.EnterpriseServices'e taşındı.

WCF'de Nesne Birleştirme

Bu makale .NET 1.1'den alınmıştır, ancak yine de Framework'ün geçerli sürümlerinde uygulanmalıdır (WCF tercih edilen yöntem olsa da).

Nesne Havuzu Oluşturma .NET


IInstanceProviderArayüzün var olduğunu göstermek için +1 , bunu çözümüm için uygulayacağım. Ben her zaman uygun bir tanım sağlarken Microsoft tarafından sağlanan bir arayüze kodumu istifleme hayranıyım.
Chris Marisic

4

Aronaught'un uygulanmasını gerçekten çok seviyorum - özellikle kaynak üzerinde bekleyen bir semaforu kullanarak kullanılabilir hale gelmesini beklediğinden. Yapmak istediğim birkaç ekleme var:

  1. Değişim sync.WaitOne()için sync.WaitOne(timeout)ve bir parametre olarak zaman aşımı maruz Acquire(int timeout)yöntemle. Bu, iş parçacığının kullanılabilir olması için bir nesnenin beklenmesi zaman aşımına uğradığında da koşulun ele alınmasını gerektirecektir.
  2. Recycle(T item)Örneğin, bir hata oluştuğunda bir nesnenin geri dönüştürülmesi gereken durumları işlemek için yöntem ekleyin .

3

Bu, havuzda sınırlı sayıda nesne bulunan başka bir uygulamadır.

public class ObjectPool<T>
    where T : class
{
    private readonly int maxSize;
    private Func<T> constructor;
    private int currentSize;
    private Queue<T> pool;
    private AutoResetEvent poolReleasedEvent;

    public ObjectPool(int maxSize, Func<T> constructor)
    {
        this.maxSize = maxSize;
        this.constructor = constructor;
        this.currentSize = 0;
        this.pool = new Queue<T>();
        this.poolReleasedEvent = new AutoResetEvent(false);
    }

    public T GetFromPool()
    {
        T item = null;
        do
        {
            lock (this)
            {
                if (this.pool.Count == 0)
                {
                    if (this.currentSize < this.maxSize)
                    {
                        item = this.constructor();
                        this.currentSize++;
                    }
                }
                else
                {
                    item = this.pool.Dequeue();
                }
            }

            if (null == item)
            {
                this.poolReleasedEvent.WaitOne();
            }
        }
        while (null == item);
        return item;
    }

    public void ReturnToPool(T item)
    {
        lock (this)
        {
            this.pool.Enqueue(item);
            this.poolReleasedEvent.Set();
        }
    }
}



Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.