Een objectgroep maken met behulp van een ConcurrentBag
In dit voorbeeld ziet u hoe u een objectgroep implementeert met een ConcurrentBag<T> objectgroep. Objectgroepen kunnen de prestaties van toepassingen verbeteren in situaties waarin u meerdere exemplaren van een klasse nodig hebt en de klasse duur is om te maken of te vernietigen. Wanneer een clientprogramma een nieuw object aanvraagt, probeert de objectgroep eerst een object op te geven dat al is gemaakt en geretourneerd aan de pool. Als er geen beschikbaar is, wordt er alleen een nieuw object gemaakt.
Het ConcurrentBag<T> wordt gebruikt om de objecten op te slaan omdat het ondersteuning biedt voor snelle invoeging en verwijdering, vooral wanneer dezelfde thread zowel items toevoegt als verwijdert. Dit voorbeeld kan verder worden uitgebreid om te worden gebouwd rond een IProducerConsumerCollection<T>, die door de gegevensstructuur van de zak wordt geïmplementeerd, zoals wel ConcurrentQueue<T> en ConcurrentStack<T>.
Tip
In dit artikel wordt gedefinieerd hoe u uw eigen implementatie van een objectgroep schrijft met een onderliggend gelijktijdig type om objecten op te slaan voor hergebruik. Het Microsoft.Extensions.ObjectPool.ObjectPool<T> type bestaat echter al onder de Microsoft.Extensions.ObjectPool naamruimte. Overweeg het beschikbare type te gebruiken voordat u uw eigen implementatie maakt, met veel extra functies.
Voorbeeld
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