C#对象池模式实现


165

有没有人有足够的资源来执行共享对象池策略,以实现有限的SQL连接池资源?(即将完全实现它是线程安全的)。

为了进一步澄清@Aaronaught请求,池使用将用于平衡对外部服务的请求。相比于我的直接观点,将其置于一种可能更容易立即理解的场景中。我有一个会话对象,其功能ISession与NHibernate中的对象类似。每个唯一的会话都管理着它与数据库的连接。当前,我有1个长时间运行的会话对象,并且遇到服务提供商正在限制我对单个会话的使用率的问题。

由于他们不希望将单个会话视为长期运行的服务帐户,因此他们显然将其视为重锤其服务的客户。这使我想到这里的问题,而不是创建一个单独的会话,而不是创建一个不同会话的池,并将请求拆分到多个会话中的服务上,而不是像我以前那样创建一个联络点。

希望背景可以提供一些价值,但可以直接回答您的一些问题:

问:创建对象是否昂贵?
答:没有对象是有限的资源池

问:它们会经常被获取/发布吗?
答:是的,他们可以再次想到NHibernate ISessions,通常在每个单页请求期间获取并释放1。

问:简单的先到先得就足够了吗?还是您需要更智能的东西,即可以防止饥饿?
答:一个简单的循环类型分配就足够了,由于饥饿,我假设您的意思是,如果没有可用的会话,则呼叫者会被阻止等待释放。这实际上并不适用,因为会话可以由不同的呼叫者共享。我的目标是在多个会话中分配使用情况,而不是在单个会话中进行分配。

我认为这可能与对象池的正常使用有所不同,这就是为什么我最初将这一部分省略并计划只是为了适应模式以允许对象共享而不是让饥饿情况发生的原因。

问:优先级,延迟加载与急切加载等事情如何?
答:不涉及优先级,为简单起见,仅假设我将在创建池本身时创建可用对象池。


1
您能告诉我们您的要求吗?并非所有的池都相等。创建对象是否昂贵?他们会经常被获取/发行吗?一个简单的先到先得就足够了吗?还是您需要更智能的东西,即可以防止饥饿?优先级,延迟加载与急切加载等事情如何处理?您可以添加的任何内容都会帮助我们(或至少是我)提出更彻底的答案。
Aaronaught

克里斯-只看您的第二段和第三段,想知道这些会议是否应该无限期地持续下去吗?听起来这是服务提供商不喜欢的(长时间运行的会话),因此您可能正在寻找一个池实现,该池实现会根据需要启动新会话,并在不使用时(在指定的时间段后)关闭它们。可以做到,但要复杂一些,所以我想确认一下。
Aaronaught

我不确定我是否需要一个强大的解决方案,因为我的解决方案只是假设的。我的服务提供商可能只是在骗我,他们的服务卖得过剩,而只是找借口怪罪用户。
克里斯·马里西奇

1
我认为TPL DataFlow BufferBlock可以满足您的大部分需求。
支出者2014年

1
线程环境中的池化是一个反复出现的问题,可以通过诸如资源池和资源缓存之类的设计模式来解决。有关更多信息请查看面向模式的软件体系结构,第3卷:资源管理模式
Fuhrmanator 2014年

Answers:


59

.NET Core中的对象池

DOTNET芯具有添加到该基类库(BCL)对象池的实施方案。您可以在此处阅读原始的GitHub问题并查看System.Buffers的代码。当前,ArrayPool是唯一可用的类型,用于合并数组。有一个很好的博客文章在这里

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

在ASP.NET Core中可以看到其用法示例。因为它在dotnet核心BCL中,所以ASP.NET Core可以与其他对象(例如Newtonsoft.Json的JSON序列化器)共享它的对象池。您可以阅读博客文章,以获得有关Newtonsoft.Json如何执行此操作的更多信息。

Microsoft Roslyn C#编译器中的对象池

新的Microsoft Roslyn C#编译器包含ObjectPool类型,该类型用于合并经常使用的对象,这些对象通常会被更新并非常频繁地收集垃圾。这减少了必须执行的垃圾收集操作的数量和大小。有一些不同的子实现都使用ObjectPool(请参阅:为什么在Roslyn中有这么多的Object Pooling实现?)。

1- SharedPools-存储20个对象的池,如果使用BigDefault,则存储100个对象。

// 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- ListPoolStringBuilderPool-不是严格分开实现,而是围绕上面显示的SharedPools实现进行包装,专门用于List和StringBuilder。因此,这将重用存储在SharedPools中的对象池。

// 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- PooledDictionaryPooledHashSet-这些直接使用ObjectPool并具有完全独立的对象池。存储128个对象的池。

// 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

该库提供MemoryStream对象池。它是的直接替代品System.IO.MemoryStream。它具有完全相同的语义。它是由Bing工程师设计的。在此处阅读博客文章或在GitHub上查看代码。

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

注意,RecyclableMemoryStreamManager应该声明一次,它将在整个过程中都有效-这就是池。如果需要,可以使用多个池。


2
这是一个很好的答案。在C#6和VS2015成为RTM之后,我很可能将其作为公认的答案,因为如果Rosyln自己对它进行如此调优,那么显然这是最好的。
克里斯·马里西奇

我同意,但是您将使用哪种实现?罗斯林包含三个。请参阅答案中指向我的问题的链接。
Muhammad Rehan Saeed

1
似乎每个鞋都有非常明确的用途,比选择适合所有鞋的开口式尺码要好得多。
克里斯·马里西奇

1
@MuhammadRehanSae与ArrayPool一起添加了很多功能
Chris Marisic '17

1
看到RecyclableMemoryStream这对于超高性能优化来说是一个了不起的补充。
克里斯·马里西奇

315

由于几个未知数,这个问题比预期的要棘手一些:池中资源的行为,对象的预期/所需生存期,需要池的真正原因等。通常,池是专用的-线程池,连接池等-因为当您确切地知道资源的作用并且更重要的是可以控制该资源的实现方式时,更容易进行优化。

既然不是那么简单,我想做的就是提供一种相当灵活的方法,您可以尝试一下,看看哪种方法效果最好。 长期道歉是预先准备的,但是在实现体面的通用资源池方面有很多基础。我真的只是在摸摸表面。

通用池必须具有一些主要的“设置”,包括:

  • 资源加载策略-渴望或懒惰;
  • 资源加载机制 -如何实际构建一个;
  • 访问策略-您提到的“循环”并不是听起来那么简单。此实现可以使用类似但不完美的循环缓冲区,因为该池无法控制何时实际回收资源。其他选项是FIFO和LIFO。FIFO将具有更多的随机访问模式,但是LIFO使实施最近最少使用的释放策略(您说过这超出了范围,但仍然值得一提)大大简化了。

对于资源加载机制,.NET已经给了我们一个干净的抽象-委托。

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

通过池的构造函数,我们就可以完成了。使用具有new()约束的泛型类型也可以工作,但这更加灵活。


在其他两个参数中,访问策略是更复杂的野兽,因此我的方法是使用基于继承(接口)的方法:

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

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

这里的概念很简单-我们将让公共Pool类处理诸如线程安全性之类的常见问题,但是为每种访问模式使用不同的“项目存储”。LIFO可以很容易地由堆栈表示,FIFO是一个队列,并且我使用了一个不是很优化但可能足够的循环缓冲区实现,该实现使用List<T>和指针来近似循环访问模式。

下面的所有类都是-的内部类,Pool<T>这是一种样式选择,但是由于这些类实际上并不是要在外部使用的Pool,因此这是最有意义的。

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

这些是显而易见的-堆栈和队列。我认为他们并不需要太多解释。循环缓冲区稍微复杂一点:

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

我可以选择许多不同的方法,但最重要的是,应该按照创建资源的顺序来访问资源,这意味着我们必须维护对它们的引用,但将其标记为“正在使用”(或不标记为“正在使用”) )。在最坏的情况下,只有一个插槽可用,并且每次获取都需要对缓冲区进行完整的迭代。如果您有成百上千个资源池,并且每秒要获取并释放它们几次,那将是很糟糕的。对于5到10个项目的池来说,这并不是一个真正的问题,在典型情况下,资源很少使用,它只需要提前一个或两个插槽即可。

请记住,这些类是私有内部类-这就是为什么它们不需要大量错误检查的原因,池本身限制了对它们的访问。

抛出一个枚举和一个工厂方法,我们就完成了这一部分:

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

下一个要解决的问题是加载策略。我定义了三种类型:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

前两个应该不言自明;第三个是混合的,它延迟加载资源,但直到池满时才真正开始重用任何资源。如果您希望池已满(听起来像您这样做),但想将实际创建它们的开销推迟到首次访问之前(例如,缩短启动时间),那么这将是一个很好的权衡。

既然我们有了item-store抽象,那么加载方法实际上并不太复杂:

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

上面的sizecount字段分别表示池的最大大小和池所拥有的资源总数(但不一定可用)。 AcquireEager最简单,它假定商店中已经有商品-这些商品将在施工时即在PreloadItems最后显示的方法中预先装载。

AcquireLazy检查池中是否有空闲物品,如果没有,它会创建一个新物品。 AcquireLazyExpanding只要池尚未达到其目标大小,它将创建一个新资源。我已尝试优化此方法以最大程度地减少锁定,并且希望我没有犯任何错误(我在多线程条件下对此进行了测试,但显然没有穷举)。

您可能想知道为什么这些方法都不费心检查存储是否已达到最大大小。一会儿,我会解决。


现在为游泳池本身。这是完整的私有数据集,其中一些已经显示:

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

回答我在上一段中提到的问题-如何确保我们限制创建的资源总数-事实证明.NET已经为此提供了一个非常好的工具,称为Semaphore,它专门用于允许固定访问资源的线程数(在这种情况下,“资源”是内部项目存储)。由于我们没有实施完整的生产者/消费者队列,因此完全可以满足我们的需求。

构造函数如下所示:

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

这里应该不足为奇。唯一需要注意的是使用PreloadItems前面已经显示的方法进行特殊加载的特殊外壳。

既然到目前为止几乎所有内容都已经被抽象地提取出来了,所以实际的AcquireRelease方法确实非常简单:

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

如前所述,我们使用Semaphore来控制并发性,而不是认真检查项目存储的状态。只要正确地释放了获得的物品,就不用担心了。

最后但并非最不重要的一点是清理:

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

IsDisposed属性的目的将很快变得清晰。主要Dispose方法真正要做的就是处置实际的合并项(如果实现)IDisposable


现在,您基本上可以按原样使用带try-finally块的代码了,但是我不喜欢这种语法,因为如果您开始在类和方法之间传递池化的资源,那么它将变得非常混乱。使用资源的主类甚至可能没有对池的引用。它确实变得非常混乱,因此更好的方法是创建一个“智能”池对象。

假设我们从以下简单的接口/类开始:

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

这是我们假装的一次性Foo资源,该资源实现IFoo并具有一些用于生成唯一身份的样板代码。我们要做的是创建另一个特殊的池对象:

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

这只是将所有“真实”方法代理到其内部IFoo(我们可以使用诸如Castle之类的动态代理库来做到这一点,但我不会理解这一点)。它还维护对Pool创建它的的引用,以便当我们创建Dispose该对象时,它会自动将其释放回池中。 除非已经处置了池,否则这意味着我们处于“清理”模式,在这种情况下,它实际上是在清理内部资源


使用上面的方法,我们可以编写如下代码:

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

这是一件非常好的事。这就是说,该代码使用IFoo(而不是其创建它的代码)实际上并不需要知道池。您甚至可以使用自己喜欢的DI库并作为提供者/工厂来注入 IFoo对象Pool<T>


我已将完整的代码放在PasteBin上,以供您复制和粘贴之用。还有一个简短的测试程序,您可以使用它来处理不同的加载/访问模式和多线程条件,以使自己确信它是线程安全的,而不是错误的。

如果您对此有任何疑问或担忧,请告诉我。


62
我在SO上阅读过的最完整,有用和有趣的答案之一。
Josh Smeaton'4

对于此响应,我完全同意@Josh的观点,尤其是对于PooledFoo部分,因为释放对象似乎总是以非常泄漏的方式进行,而且我认为可以使用using方法是最有意义的。像您所示的结构,我只是还没有坐下来,并试图建立一个结构,以便您的答案为我提供解决问题所需的所有信息。我认为对于我的特定情况,我可以简化很多工作,因为我可以在线程之间共享实例,而无需将其释放回池中。
克里斯·马里西奇

但是,如果简单的方法不能首先解决问题,那么我脑海中浮现出一些想法,以了解如何明智地处理案件。我认为,最具体地说,我将建立发行版,以便能够确定该会话本身有故障并进行处理,然后将新的会话替换到池中。不管现在的这篇文章是C#3.0中关于对象池的权威指南,我都希望看到是否有人对此有更多评论。
克里斯·马里西奇

@Chris:如果您正在谈论WCF客户端代理,那么我也有一个模式,尽管您需要依赖注入器或方法拦截器才能有效地使用它。DI版本将内核与自定义提供程序一起使用,以获取新的出现故障的版本,方法拦截版本(我的偏爱)只是包装了现有的代理,并在每个代理之前插入了错误检查。我不确定将其集成到这样的池中有多么容易(尚未尝试过,因为我只是写了此!),但肯定有可能。
Aaronaught

5
非常令人印象深刻,尽管在大多数情况下设计过度。我希望这样的事情会成为框架的一部分。
ChaosPandion 2010年

7

这样的事情可能适合您的需求。

/// <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)
                {

                }
            }
        }
    }
}

用法示例

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
刮擦先前的评论。我想我觉得很奇怪,因为该池似乎没有任何阈值,也许不需要,它取决于要求。
Aaronaught

1
@Aaronaught-真的那么奇怪吗?我想创建一个仅提供所需功能的轻量级池。客户端可以正确使用该类。
ChaosPandion'4

1
+1是一个非常简单的解决方案,只需将后备类型更改为List / HashTable等,然后将计数器更改为翻转即可适应我的目的。随机的问题,您如何处理池对象本身的管理?您是否只是将其粘贴到IOC容器中以将其定义为单例?
克里斯·马里西奇

1
那应该是静态只读吗?但是,我觉得奇怪的是,如果在例外情况下将put放入finally语句中,那么它本身的对象是否有故障不是很可能吗?您是否会在Put方法内部处理该问题,并为简单起见而将其省略,以便进行某种类型的检查以检查对象是否有故障,并创建一个要添加到池中的新实例,而不是插入前一个实例?
克里斯·马里西奇

1
@Chris-我只是提供一个过去发现有用的简单工具。其余的取决于您。修改并使用您认为合适的代码。
ChaosPandion 2010年


4

回到过去,Microsoft通过Microsoft Transaction Server(MTS)和更高版本的COM +提供了一个框架来为COM对象进行对象池化。该功能已转发到.NET Framework中的System.EnterpriseServices和Windows Communication Foundation中。

WCF中的对象池

本文来自.NET 1.1,但仍应适用于当前版本的Framework(即使WCF是首选方法)。

对象池.NET


+1表示IInstanceProvider接口已经存在,因为我将为我的解决方案实现该接口。当我提供合适的定义时,我总是喜欢将代码堆叠在Microsoft提供的界面后面。
克里斯·马里西奇

4

我非常喜欢Aronaught的实现-尤其是因为他通过使用信号灯来处理等待资源以变得可用。我想补充几个内容:

  1. 更改sync.WaitOne()sync.WaitOne(timeout)超时并将其作为Acquire(int timeout)方法的参数公开。当线程超时等待对象变为可用时,这也将需要处理条件。
  2. 例如,添加Recycle(T item)方法来处理发生故障时需要回收对象的情况。

3

这是另一种实现,其中池中的对象数量有限。

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



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.