Dela via


Skapa en objektpool med hjälp av en ConcurrentBag

Det här exemplet visar hur du använder en ConcurrentBag<T> för att implementera en objektpool. Objektpooler kan förbättra programprestanda i situationer där du behöver flera instanser av en klass och klassen är dyr att skapa eller förstöra. När ett klientprogram begär ett nytt objekt försöker objektpoolen först ange ett som redan har skapats och returnerats till poolen. Om inget är tillgängligt skapas bara ett nytt objekt.

ConcurrentBag<T> Används för att lagra objekten eftersom den stöder snabb infogning och borttagning, särskilt när samma tråd både lägger till och tar bort objekt. Det här exemplet kan utökas ytterligare för att byggas runt en IProducerConsumerCollection<T>, som påsdatastrukturen implementerar, precis som ConcurrentQueue<T> och ConcurrentStack<T>.

Dricks

Den här artikeln definierar hur du skriver en egen implementering av en objektpool med en underliggande samtidig typ för att lagra objekt för återanvändning. Typen finns dock Microsoft.Extensions.ObjectPool.ObjectPool<T> redan under Microsoft.Extensions.ObjectPool namnområdet. Överväg att använda den tillgängliga typen innan du skapar en egen implementering, som innehåller många ytterligare funktioner.

Exempel

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace ObjectPoolExample
{
    public class ObjectPool<T>
    {
        private readonly ConcurrentBag<T> _objects;
        private readonly Func<T> _objectGenerator;

        public ObjectPool(Func<T> objectGenerator)
        {
            _objectGenerator = objectGenerator ?? throw new ArgumentNullException(nameof(objectGenerator));
            _objects = new ConcurrentBag<T>();
        }

        public T Get() => _objects.TryTake(out T item) ? item : _objectGenerator();

        public void Return(T item) => _objects.Add(item);
    }

    class Program
    {
        static void Main(string[] args)
        {
            using var cts = new CancellationTokenSource();

            // Create an opportunity for the user to cancel.
            _ = Task.Run(() =>
            {
                if (char.ToUpperInvariant(Console.ReadKey().KeyChar) == 'C')
                {
                    cts.Cancel();
                }
            });

            var pool = new ObjectPool<ExampleObject>(() => new ExampleObject());

            // Create a high demand for ExampleObject instance.
            Parallel.For(0, 1000000, (i, loopState) =>
            {
                var example = pool.Get();
                try
                {
                    Console.CursorLeft = 0;
                    // This is the bottleneck in our application. All threads in this loop
                    // must serialize their access to the static Console class.
                    Console.WriteLine($"{example.GetValue(i):####.####}");
                }
                finally
                {
                    pool.Return(example);
                }

                if (cts.Token.IsCancellationRequested)
                {
                    loopState.Stop();
                }
            });

            Console.WriteLine("Press the Enter key to exit.");
            Console.ReadLine();
        }
    }

    // A toy class that requires some resources to create.
    // You can experiment here to measure the performance of the
    // object pool vs. ordinary instantiation.
    class ExampleObject
    {
        public int[] Nums { get; set; }

        public ExampleObject()
        {
            Nums = new int[1000000];
            var rand = new Random();
            for (int i = 0; i < Nums.Length; i++)
            {
                Nums[i] = rand.Next();
            }
        }

        public double GetValue(long i) => Math.Sqrt(Nums[i]);
    }
}
Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks

Module ObjectPoolExample


    Public Class ObjectPool(Of T)
        Private _objects As ConcurrentBag(Of T)
        Private _objectGenerator As Func(Of T)

        Public Sub New(ByVal objectGenerator As Func(Of T))
            If objectGenerator Is Nothing Then Throw New ArgumentNullException("objectGenerator")
            _objects = New ConcurrentBag(Of T)()
            _objectGenerator = objectGenerator
        End Sub

        Public Function GetObject() As T
            Dim item As T
            If _objects.TryTake(item) Then Return item
            Return _objectGenerator()
        End Function

        Public Sub PutObject(ByVal item As T)
            _objects.Add(item)
        End Sub
    End Class


    Sub Main()

        Dim cts As CancellationTokenSource = New CancellationTokenSource()

        ' Create an opportunity for the user to cancel.
        Task.Run(Sub()
                     If Console.ReadKey().KeyChar = "c"c Or Console.ReadKey().KeyChar = "C"c Then
                         cts.Cancel()
                     End If
                 End Sub)
        Dim pool As ObjectPool(Of TestClass) = New ObjectPool(Of TestClass)(Function() New TestClass())

        ' Create a high demand for TestClass objects.
        Parallel.For(0, 1000000, Sub(i, loopState)

                                     Dim mc As TestClass = pool.GetObject()
                                     Console.CursorLeft = 0
                                     ' This is the bottleneck in our application. All threads in this loop
                                     ' must serialize their access to the static Console class.
                                     Console.WriteLine("{0:####.####}", mc.GetValue(i))

                                     pool.PutObject(mc)
                                     If cts.Token.IsCancellationRequested Then
                                         loopState.Stop()

                                     End If
                                 End Sub)

        Console.WriteLine("Press the Enter key to exit.")
        Console.ReadLine()
        cts.Dispose()
    End Sub

    ' A toy class that requires some resources to create.
    ' You can experiment here to measure the performance of the
    ' object pool vs. ordinary instantiation.
    Class TestClass

        Public Nums() As Integer

        Public Function GetValue(ByVal i As Long) As Double
            Return Math.Sqrt(Nums(i))
        End Function

        Public Sub New()

            Nums = New Integer(10000000) {}
            '   ReDim Nums(1000000)
            Dim rand As Random = New Random()
            For i As Integer = 0 To Nums.Length - 1
                Nums(i) = rand.Next()
            Next
        End Sub
    End Class

End Module

Se även