Zarządzanie pamięcią i wyrzucanie elementów bezużytecznych (GC) w ASP.NET CoreMemory management and garbage collection (GC) in ASP.NET Core

Autorzy Sébastien ros i Rick AndersonBy Sébastien Ros and Rick Anderson

Zarządzanie pamięcią jest złożone, nawet w zarządzanym środowisku, takim jak .NET.Memory management is complex, even in a managed framework like .NET. Analizowanie i rozwiązywanie problemów z pamięcią może być trudne.Analyzing and understanding memory issues can be challenging. W tym artykule:This article:

  • Został umotywowany przez wiele przecieków pamięci i nie działa problem z GC.Was motivated by many memory leak and GC not working issues. Większość z tych problemów została spowodowana przez niezrozumienie, jak zużycie pamięci działa w programie .NET Core, lub nie zrozumienie, jak to jest mierzone.Most of these issues were caused by not understanding how memory consumption works in .NET Core, or not understanding how it's measured.
  • Pokazuje problematyczne użycie pamięci i sugeruje alternatywne podejścia.Demonstrates problematic memory use, and suggests alternative approaches.

Jak działa wyrzucanie elementów bezużytecznych w programie .NET CoreHow garbage collection (GC) works in .NET Core

GC przypisuje segmenty sterty, w których każdy segment jest ciągłym zakresem pamięci.The GC allocates heap segments where each segment is a contiguous range of memory. Obiekty umieszczone w stercie są podzielone na jedną z trzech generacji: 0, 1 lub 2.Objects placed in the heap are categorized into one of 3 generations: 0, 1, or 2. Generacja określa częstotliwość, z jaką system GC próbuje zwolnić pamięć na zarządzanych obiektach, do których nie odwołuje się już aplikacja.The generation determines the frequency the GC attempts to release memory on managed objects that are no longer referenced by the app. Niższe numerowane generacji są częścią pamięci.Lower numbered generations are GC'd more frequently.

Obiekty są przenoszone z jednej generacji na inną w zależności od ich okresu istnienia.Objects are moved from one generation to another based on their lifetime. Gdy obiekty są już na żywo, są przenoszone do wyższego poziomu generacji.As objects live longer, they are moved into a higher generation. Jak wspomniano wcześniej, wyższe generacji są krótsze.As mentioned previously, higher generations are GC'd less often. Niekrótkoterminowe obiekty pozostają zawsze w generacji 0.Short term lived objects always remain in generation 0. Na przykład obiekty, do których istnieją odwołania w czasie trwania żądania sieci Web, są krótkotrwałe.For example, objects that are referenced during the life of a web request are short lived. Pojedyncze aplikacje są zwykle migrowane do generacji 2.Application level singletons generally migrate to generation 2.

Po uruchomieniu aplikacji ASP.NET Core, GC:When an ASP.NET Core app starts, the GC:

  • Rezerwuje pewną ilość pamięci dla początkowych segmentów sterty.Reserves some memory for the initial heap segments.
  • Zatwierdza małą część pamięci podczas ładowania środowiska uruchomieniowego.Commits a small portion of memory when the runtime is loaded.

Powyższe alokacje pamięci są wykonywane ze względu na wydajność.The preceding memory allocations are done for performance reasons. Korzyść wydajności pochodzi z segmentów sterty w ciągłej pamięci.The performance benefit comes from heap segments in contiguous memory.

Wywołaj metodę GC. KolekcjonowaCall GC.Collect

Wywoływanie GC. Zbierz jawnie:Calling GC.Collect explicitly:

  • Nie należy wykonywać według ASP.NET Core produkcyjnych aplikacji.Should not be done by production ASP.NET Core apps.
  • Jest przydatne podczas badania przecieków pamięci.Is useful when investigating memory leaks.
  • Podczas badania program sprawdza, czy moduł GC usunął wszystkie obiekty zawieszonego z pamięci, aby można było mierzyć pamięć.When investigating, verifies the GC has removed all dangling objects from memory so memory can be measured.

Analizowanie użycia pamięci przez aplikacjęAnalyzing the memory usage of an app

Narzędzia dedykowane ułatwiają Analizowanie użycia pamięci:Dedicated tools can help analyzing memory usage:

  • Liczenie odwołań do obiektówCounting object references
  • Mierzenie, ile ma wpływ na wykorzystanie procesora CPU przez moduł GCMeasuring how much impact the GC has on CPU usage
  • Mierzenie przestrzeni pamięci używanej dla każdej generacjiMeasuring memory space used for each generation

Użyj następujących narzędzi do analizowania użycia pamięci:Use the following tools to analyze memory usage:

Wykrywanie problemów z pamięciąDetecting memory issues

Za pomocą Menedżera zadań można uzyskać informacje o ilości pamięci używanej przez aplikację ASP.NET.Task Manager can be used to get an idea of how much memory an ASP.NET app is using. Wartość pamięci Menedżera zadań:The Task Manager memory value:

  • Przedstawia ilość pamięci używanej przez proces ASP.NET.Represents the amount of memory that is used by the ASP.NET process.
  • Obejmuje żywe obiekty i innych odbiorców pamięci, takich jak użycie pamięci natywnej.Includes the app's living objects and other memory consumers such as native memory usage.

Jeśli wartość pamięci Menedżera zadań zwiększy się w nieskończoność i nigdy nie zostanie spłaszczona, aplikacja ma przeciek pamięci.If the Task Manager memory value increases indefinitely and never flattens out, the app has a memory leak. W poniższych sekcjach przedstawiono i wyjaśniono kilka wzorców użycia pamięci.The following sections demonstrate and explain several memory usage patterns.

Przykładowa aplikacja do wyświetlania pamięciSample display memory usage app

Przykładowa aplikacja MemoryLeak jest dostępna w witrynie GitHub.The MemoryLeak sample app is available on GitHub. Aplikacja MemoryLeak:The MemoryLeak app:

  • Obejmuje kontroler diagnostyczny, który zbiera dane pamięci w czasie rzeczywistym i GC dla aplikacji.Includes a diagnostic controller that gathers real-time memory and GC data for the app.
  • Zawiera stronę indeksu wyświetlającą pamięć i dane GC.Has an Index page that displays the memory and GC data. Strona indeks jest odświeżana co sekundę.The Index page is refreshed every second.
  • Zawiera kontroler interfejsu API, który udostępnia różne wzorce obciążenia pamięci.Contains an API controller that provides various memory load patterns.
  • Nie jest to obsługiwane narzędzie, ale może służyć do wyświetlania wzorców użycia pamięci ASP.NET Core aplikacji.Is not a supported tool, however, it can be used to display memory usage patterns of ASP.NET Core apps.

Uruchom MemoryLeak.Run MemoryLeak. Przydzieloną pamięć powoli wzrasta do momentu wystąpienia wykazu globalnego.Allocated memory slowly increases until a GC occurs. Pamięć rośnie, ponieważ narzędzie przydziela obiekt niestandardowy do przechwytywania danych.Memory increases because the tool allocates custom object to capture data. Na poniższej ilustracji przedstawiono stronę indeksu MemoryLeak w przypadku wystąpienia generacji 0 GC.The following image shows the MemoryLeak Index page when a Gen 0 GC occurs. Wykres pokazuje 0 RPS pliku (liczba żądań na sekundę), ponieważ nie wywołano punktów końcowych interfejsu API z kontrolera interfejsu API.The chart shows 0 RPS (Requests per second) because no API endpoints from the API controller have been called.

Poprzedni wykres

Na wykresie są wyświetlane dwie wartości użycia pamięci:The chart displays two values for the memory usage:

  • Przydzielone: ilość pamięci zajętej przez zarządzane obiektyAllocated: the amount of memory occupied by managed objects
  • Zestaw roboczy: zestaw stron w wirtualnej przestrzeni adresowej procesu, który jest obecnie rezydentem pamięci fizycznej.Working set: The set of pages in the virtual address space of the process that are currently resident in physical memory. Wyświetlany zestaw roboczy jest taka sama jak wartość w Menedżerze zadań.The working set shown is the same value Task Manager displays.

Obiekty przejścioweTransient objects

Poniższy interfejs API tworzy wystąpienie ciągu 10 KB i zwraca go do klienta.The following API creates a 10-KB String instance and returns it to the client. W przypadku każdego żądania nowy obiekt zostaje przydzielony w pamięci i zapisany w odpowiedzi.On each request, a new object is allocated in memory and written to the response. Ciągi są przechowywane jako znaki UTF-16 w programie .NET, więc każdy znak pobiera 2 bajty w pamięci.Strings are stored as UTF-16 characters in .NET so each character takes 2 bytes in memory.

[HttpGet("bigstring")]
public ActionResult<string> GetBigString()
{
    return new String('x', 10 * 1024);
}

Poniższy wykres jest generowany z stosunkowo małym obciążeniem, aby pokazać, w jaki sposób przydziały pamięci mają wpływ system GC.The following graph is generated with a relatively small load in to show how memory allocations are impacted by the GC.

Poprzedni wykres

Powyższy wykres przedstawia:The preceding chart shows:

  • 4K RPS pliku (żądania na sekundę).4K RPS (Requests per second).
  • Kolekcje GC generacji 0 są wykonywane co dwa sekundy.Generation 0 GC collections occur about every two seconds.
  • Zestaw roboczy jest stały o około 500 MB.The working set is constant at approximately 500 MB.
  • Procesor CPU wynosi 12%.CPU is 12%.
  • Użycie pamięci i wydanie (za pomocą GC) jest stabilne.The memory consumption and release (through GC) is stable.

Na poniższym wykresie przedstawiono maksymalną przepływność, która może być obsługiwana przez maszynę.The following chart is taken at the max throughput that can be handled by the machine.

Poprzedni wykres

Powyższy wykres przedstawia:The preceding chart shows:

  • 22K RPS PLIKU22K RPS
  • Kolekcje GC generacji 0 są wykonywane kilka razy na sekundę.Generation 0 GC collections occur several times per second.
  • Kolekcje 1 generacji są wyzwalane, ponieważ aplikacja przydzieliła znacznie więcej pamięci na sekundę.Generation 1 collections are triggered because the app allocated significantly more memory per second.
  • Zestaw roboczy jest stały o około 500 MB.The working set is constant at approximately 500 MB.
  • Procesor CPU wynosi 33%.CPU is 33%.
  • Użycie pamięci i wydanie (za pomocą GC) jest stabilne.The memory consumption and release (through GC) is stable.
  • Procesor CPU (33%) nie jest nadmiernie wykorzystane, dlatego wyrzucanie elementów bezużytecznych może obsłużyć dużą liczbę alokacji.The CPU (33%) is not over-utilized, therefore the garbage collection can keep up with a high number of allocations.

Stacja robocza GC a serwer GCWorkstation GC vs. Server GC

Moduł wyrzucania elementów bezużytecznych platformy .NET ma dwa różne tryby:The .NET Garbage Collector has two different modes:

  • Stacja robocza GC : zoptymalizowana dla pulpitu.Workstation GC : Optimized for the desktop.
  • Serwer GC .Server GC . Domyślna wartość GC dla aplikacji ASP.NET Core.The default GC for ASP.NET Core apps. Zoptymalizowany pod kątem serwera.Optimized for the server.

Tryb GC można jawnie ustawić w pliku projektu lub w runtimeconfig.jsna pliku opublikowanej aplikacji.The GC mode can be set explicitly in the project file or in the runtimeconfig.json file of the published app. Następujące znaczniki pokazują ustawienia ServerGarbageCollection w pliku projektu:The following markup shows setting ServerGarbageCollection in the project file:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Zmiana ServerGarbageCollection w pliku projektu wymaga odbudowania aplikacji.Changing ServerGarbageCollection in the project file requires the app to be rebuilt.

Uwaga: Zbieranie elementów bezużytecznych serwera nie jest dostępne na maszynach z jednym rdzeniem.Note: Server garbage collection is not available on machines with a single core. Aby uzyskać więcej informacji, zobacz IsServerGC.For more information, see IsServerGC.

Na poniższej ilustracji przedstawiono profil pamięci w ramach 5 K RPS pliku przy użyciu usługi Stacja robocza GC.The following image shows the memory profile under a 5K RPS using the Workstation GC.

Poprzedni wykres

Różnice między tym wykresem a wersją serwera są istotne:The differences between this chart and the server version are significant:

  • Zestaw roboczy spadnie od 500 MB do 70 MB.The working set drops from 500 MB to 70 MB.
  • GC wykonuje wiele kolekcji generacji 0 na sekundę, a nie co dwa sekundy.The GC does generation 0 collections multiple times per second instead of every two seconds.
  • Wykaz globalny spadnie od 300 MB do 10 MB.GC drops from 300 MB to 10 MB.

W typowym środowisku serwera sieci Web użycie procesora CPU jest ważniejsze niż pamięć, dlatego serwer GC jest lepszy.On a typical web server environment, CPU usage is more important than memory, therefore the Server GC is better. Jeśli użycie pamięci jest wysokie i użycie procesora CPU jest stosunkowo niskie, stacja robocza GC może być bardziej wydajna.If memory utilization is high and CPU usage is relatively low, the Workstation GC might be more performant. Na przykład duża gęstość hostowanie kilku aplikacji sieci Web, w których ilość pamięci jest niestateczna.For example, high density hosting several web apps where memory is scarce.

GC przy użyciu platformy Docker i małych kontenerówGC using Docker and small containers

Gdy wiele aplikacji kontenerowych jest uruchomionych na jednym komputerze, stacja robocza GC może być bardziej niezależna od serwera GC.When multiple containerized apps are running on one machine, Workstation GC might be more preformant than Server GC. Aby uzyskać więcej informacji, zobacz Uruchamianie z serwerem GC w niewielkim kontenerze i Uruchamianie z serwerem GC w niewielkim scenariuszu kontenera część 1 — stały limit dla sterty GC.For more information, see Running with Server GC in a Small Container and Running with Server GC in a Small Container Scenario Part 1 – Hard Limit for the GC Heap.

Trwałe odwołania do obiektówPersistent object references

GC nie może zwolnić obiektów, do których istnieją odwołania.The GC cannot free objects that are referenced. Obiekty, do których istnieją odwołania, ale nie są już potrzebne, powodują przeciek pamięci.Objects that are referenced but no longer needed result in a memory leak. Jeśli aplikacja często przydziela obiekty i nie może ich zwolnić, gdy nie są już potrzebne, użycie pamięci zwiększy się z upływem czasu.If the app frequently allocates objects and fails to free them after they are no longer needed, memory usage will increase over time.

Poniższy interfejs API tworzy wystąpienie ciągu 10 KB i zwraca go do klienta.The following API creates a 10-KB String instance and returns it to the client. Różnica w poprzednim przykładzie polega na tym, że to wystąpienie jest przywoływane przez statyczną składową, co oznacza, że nigdy nie jest dostępna dla kolekcji.The difference with the previous example is that this instance is referenced by a static member, which means it's never available for collection.

private static ConcurrentBag<string> _staticStrings = new ConcurrentBag<string>();

[HttpGet("staticstring")]
public ActionResult<string> GetStaticString()
{
    var bigString = new String('x', 10 * 1024);
    _staticStrings.Add(bigString);
    return bigString;
}

Powyższy kod ma następujące działanie:The preceding code:

  • Jest przykładem typowego przecieku pamięci.Is an example of a typical memory leak.
  • Częste wywołania powodują, że pamięć aplikacji wzrasta do momentu awarii procesu z OutOfMemory wyjątkiem.With frequent calls, causes app memory to increases until the process crashes with an OutOfMemory exception.

Poprzedni wykres

Na powyższym obrazie:In the preceding image:

  • Testowanie obciążenia /api/staticstring punktu końcowego powoduje wzrost liniowy w pamięci.Load testing the /api/staticstring endpoint causes a linear increase in memory.
  • Proces GC próbuje zwolnić pamięć w miarę wzrostu ilości pamięci, wywołując kolekcję generacji 2.The GC tries to free memory as the memory pressure grows, by calling a generation 2 collection.
  • GC nie może zwolnić ilości pamięci.The GC cannot free the leaked memory. Alokacja i zestaw roboczy zwiększają się wraz z czasem.Allocated and working set increase with time.

Niektóre scenariusze, takie jak buforowanie, wymagają, aby odwołania do obiektów były przechowywane do momentu wymuszenia zwolnienia pamięci.Some scenarios, such as caching, require object references to be held until memory pressure forces them to be released. WeakReferenceKlasa może być używana dla tego typu kodu buforowania.The WeakReference class can be used for this type of caching code. WeakReferenceObiekt jest zbierany pod ciśnieniem pamięci.A WeakReference object is collected under memory pressures. Domyślna implementacja programu IMemoryCache WeakReference .The default implementation of IMemoryCache uses WeakReference.

Pamięć natywnaNative memory

Niektóre obiekty .NET Core są zależne od pamięci natywnej.Some .NET Core objects rely on native memory. Pamięć natywna nie może być zbierana przez GC.Native memory can not be collected by the GC. Obiekt .NET używający pamięci natywnej musi być bezpłatny przy użyciu kodu natywnego.The .NET object using native memory must free it using native code.

Platforma .NET udostępnia IDisposable interfejs, dzięki któremu deweloperzy mogą zwolnić pamięć natywną..NET provides the IDisposable interface to let developers release native memory. Nawet jeśli Dispose nie jest wywoływana, prawidłowo zaimplementowane klasy są wywoływane Dispose po uruchomieniu finalizatora .Even if Dispose is not called, correctly implemented classes call Dispose when the finalizer runs.

Spójrzmy na poniższy kod:Consider the following code:

[HttpGet("fileprovider")]
public void GetFileProvider()
{
    var fp = new PhysicalFileProvider(TempPath);
    fp.Watch("*.*");
}

PhysicalFileProvider jest klasą zarządzaną, więc każde wystąpienie zostanie zebrane na końcu żądania.PhysicalFileProvider is a managed class, so any instance will be collected at the end of the request.

Na poniższej ilustracji przedstawiono profil pamięci podczas fileprovider ciągłego wywoływania interfejsu API.The following image shows the memory profile while invoking the fileprovider API continuously.

Poprzedni wykres

Powyższy wykres przedstawia oczywisty problem z implementacją tej klasy, ponieważ zwiększa użycie pamięci.The preceding chart shows an obvious issue with the implementation of this class, as it keeps increasing memory usage. Jest to znany problem, który jest śledzony w tym problemie.This is a known problem that is being tracked in this issue.

Ten sam wyciek może wystąpić w kodzie użytkownika, wykonując jedną z następujących czynności:The same leak could be happen in user code, by one of the following:

  • Nie zwalniaj klasy prawidłowo.Not releasing the class correctly.
  • Zapominanie o wywołaniu Dispose metody obiektów zależnych, które powinny zostać usunięte.Forgetting to invoke the Disposemethod of the dependent objects that should be disposed.

Sterta dużych obiektówLarge objects heap

Częste alokacje pamięci/wolne cykle mogą fragmentacji pamięci, szczególnie podczas przydzielania dużych fragmentów pamięci.Frequent memory allocation/free cycles can fragment memory, especially when allocating large chunks of memory. Obiekty są przydzielono w ciągłych blokach pamięci.Objects are allocated in contiguous blocks of memory. W celu ograniczenia fragmentacji, gdy pamięć podwolna zostanie zwolniona, próbuje ją zdefragmentować.To mitigate fragmentation, when the GC frees memory, it tries to defragment it. Ten proces jest nazywany kompaktowania .This process is called compaction . Kompaktowanie obejmuje przeniesienie obiektów.Compaction involves moving objects. Przeniesienie dużych obiektów nakłada spadek wydajności.Moving large objects imposes a performance penalty. Z tego powodu w wykazie globalnym tworzona jest specjalna strefa pamięci dla dużych obiektów, nazywana stertą dużego obiektu (LOH).For this reason the GC creates a special memory zone for large objects, called the large object heap (LOH). Obiekty, które są większe niż 85 000 bajtów (około 83 KB) są następujące:Objects that are greater than 85,000 bytes (approximately 83 KB) are:

  • Umieszczone na LOH.Placed on the LOH.
  • Nie kompaktuje.Not compacted.
  • Zbierane podczas operacje odzyskiwania pamięci generacji 2.Collected during generation 2 GCs.

Gdy LOH jest zapełniony, GC wywoła kolekcję generacji 2.When the LOH is full, the GC will trigger a generation 2 collection. Kolekcje 2 generacji:Generation 2 collections:

  • Są z założenia powolne.Are inherently slow.
  • Dodatkowo Powiąż koszt wyzwolenia kolekcji na wszystkich innych generacjach.Additionally incur the cost of triggering a collection on all other generations.

Następujący kod kompaktuje LOH od razu:The following code compacts the LOH immediately:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();

Zobacz LargeObjectHeapCompactionMode , aby uzyskać informacje na temat kompaktowania LOH.See LargeObjectHeapCompactionMode for information on compacting the LOH.

W kontenerach korzystających z platformy .NET Core 3,0 i nowszych LOH jest automatycznie kompaktowana.In containers using .NET Core 3.0 and later, the LOH is automatically compacted.

Poniższy interfejs API, który ilustruje to zachowanie:The following API that illustrates this behavior:

[HttpGet("loh/{size=85000}")]
public int GetLOH1(int size)
{
   return new byte[size].Length;
}

Na poniższym wykresie przedstawiono profil pamięci wywołania /api/loh/84975 punktu końcowego w obszarze maksymalne obciążenie:The following chart shows the memory profile of calling the /api/loh/84975 endpoint, under maximum load:

Poprzedni wykres

Na poniższym wykresie przedstawiono profil pamięci wywołania /api/loh/84976 punktu końcowego, przydzielanie tylko jednego bajtu :The following chart shows the memory profile of calling the /api/loh/84976 endpoint, allocating just one more byte :

Poprzedni wykres

Uwaga: byte[] Struktura zawiera bajty dodatkowe.Note: The byte[] structure has overhead bytes. Dlatego 84 976 bajtów wyzwala limit 85 000.That's why 84,976 bytes triggers the 85,000 limit.

Porównanie dwóch wcześniejszych wykresów:Comparing the two preceding charts:

  • Zestaw roboczy jest podobny do obu scenariuszy, około 450 MB.The working set is similar for both scenarios, about 450 MB.
  • W obszarze żądania LOH (84 975 bajtów) przedstawiono większość kolekcji generacji 0.The under LOH requests (84,975 bytes) shows mostly generation 0 collections.
  • Żądania over LOH generują kolekcje stałej generacji 2.The over LOH requests generate constant generation 2 collections. Kolekcje generacji 2 są kosztowne.Generation 2 collections are expensive. Wymagany jest większy procesor CPU, a przepływność spadnie niemal 50% czasu.More CPU is required and throughput drops almost 50%.

Tymczasowe duże obiekty są szczególnie problematyczne, ponieważ powodują Gen2 operacje odzyskiwania pamięci.Temporary large objects are particularly problematic because they cause gen2 GCs.

W celu uzyskania maksymalnej wydajności należy zminimalizować użycie dużego obiektu.For maximum performance, large object use should be minimized. Jeśli to możliwe, rozdziel duże obiekty.If possible, split up large objects. Na przykład buforowanie z pamięci podręcznej w ASP.NET Core podzielić wpisy pamięci podręcznej na bloki mniejsze niż 85 000 bajtów.For example, Response Caching middleware in ASP.NET Core split the cache entries into blocks less than 85,000 bytes.

Następujące linki pokazują ASP.NET Core podejście do zachowywania obiektów w LOH limicie:The following links show the ASP.NET Core approach to keeping objects under the LOH limit:

Aby uzyskać więcej informacji, zobacz:For more information, see:

HttpClientHttpClient

Nieprawidłowe użycie HttpClient może skutkować wyciekiem zasobów.Incorrectly using HttpClient can result in a resource leak. Zasoby systemowe, takie jak połączenia z bazami danych, gniazda, uchwyty plików itp.:System resources, such as database connections, sockets, file handles, etc.:

  • Jest bardziej nieograniczony niż pamięć.Are more scarce than memory.
  • Są bardziej problematyczne w przypadku przecieków od pamięci.Are more problematic when leaked than memory.

Doświadczeni Deweloperzy platformy .NET wiedzą, jak odwoływać się do Dispose obiektów, które implementują IDisposable .Experienced .NET developers know to call Dispose on objects that implement IDisposable. Nieusuwania obiektów, które implementują IDisposable zwykle, powoduje przeciek pamięci lub przeciek zasobów systemu.Not disposing objects that implement IDisposable typically results in leaked memory or leaked system resources.

HttpClient implementuje IDisposable , ale nie powinien być usuwany przy każdym wywołaniu.HttpClient implements IDisposable, but should not be disposed on every invocation. Należy raczej HttpClient ponownie użyć.Rather, HttpClient should be reused.

Następujący punkt końcowy tworzy i usuwa nowe HttpClient wystąpienie dla każdego żądania:The following endpoint creates and disposes a new HttpClient instance on every request:

[HttpGet("httpclient1")]
public async Task<int> GetHttpClient1(string url)
{
    using (var httpClient = new HttpClient())
    {
        var result = await httpClient.GetAsync(url);
        return (int)result.StatusCode;
    }
}

W obszarze obciążenie rejestrowane są następujące komunikaty o błędach:Under load, the following error messages are logged:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HLG70PBE1CR1", Request id "0HLG70PBE1CR1:00000031":
      An unhandled exception was thrown by the application.
System.Net.Http.HttpRequestException: Only one usage of each socket address
    (protocol/network address/port) is normally permitted --->
    System.Net.Sockets.SocketException: Only one usage of each socket address
    (protocol/network address/port) is normally permitted
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port,
    CancellationToken cancellationToken)

Nawet jeśli HttpClient wystąpienia zostaną usunięte, rzeczywiste połączenie sieciowe zajmuje trochę czasu, aby zwolnić przez system operacyjny.Even though the HttpClient instances are disposed, the actual network connection takes some time to be released by the operating system. Nieustannie tworząc nowe połączenia, nastąpi wyczerpanie portów .By continuously creating new connections, ports exhaustion occurs. Każde połączenie klienta wymaga własnego portu klienta.Each client connection requires its own client port.

Jednym ze sposobów zapobiegania wyczerpaniu portów jest ponowne użycie tego samego HttpClient wystąpienia:One way to prevent port exhaustion is to reuse the same HttpClient instance:

private static readonly HttpClient _httpClient = new HttpClient();

[HttpGet("httpclient2")]
public async Task<int> GetHttpClient2(string url)
{
    var result = await _httpClient.GetAsync(url);
    return (int)result.StatusCode;
}

HttpClientWystąpienie jest wydawany, gdy aplikacja zostanie zatrzymana.The HttpClient instance is released when the app stops. Ten przykład pokazuje, że nie każdy zasób jednorazowy powinien zostać usunięty po każdym użyciu.This example shows that not every disposable resource should be disposed after each use.

Aby lepiej obsługiwać okres istnienia wystąpienia, zobacz następujące tematy HttpClient :See the following for a better way to handle the lifetime of an HttpClient instance:

Buforowanie obiektówObject pooling

W poprzednim przykładzie pokazano, jak HttpClient wystąpienie może być statyczne i ponownie używane przez wszystkie żądania.The previous example showed how the HttpClient instance can be made static and reused by all requests. Ponowne użycie uniemożliwia uruchomienie zasobów.Reuse prevents running out of resources.

Buforowanie obiektów:Object pooling:

  • Używa wzorca ponownego użycia.Uses the reuse pattern.
  • Jest przeznaczony dla obiektów, które są kosztowne do utworzenia.Is designed for objects that are expensive to create.

Pula to kolekcja wstępnie zainicjowanych obiektów, które można zarezerwować i zwolnić między wątkami.A pool is a collection of pre-initialized objects that can be reserved and released across threads. Pule mogą definiować reguły alokacji, takie jak limity, wstępnie zdefiniowane rozmiary lub szybkość wzrostu.Pools can define allocation rules such as limits, predefined sizes, or growth rate.

Pakiet NuGet Microsoft. Extensions. Objectpool zawiera klasy, które pomagają zarządzać takimi pulami.The NuGet package Microsoft.Extensions.ObjectPool contains classes that help to manage such pools.

Następujący punkt końcowy interfejsu API tworzy wystąpienie byte buforu, który jest wypełniony liczbami losowymi dla każdego żądania:The following API endpoint instantiates a byte buffer that is filled with random numbers on each request:

        [HttpGet("array/{size}")]
        public byte[] GetArray(int size)
        {
            var random = new Random();
            var array = new byte[size];
            random.NextBytes(array);

            return array;
        }

Na poniższym wykresie przedstawiono wywoływanie poprzedzającego interfejsu API o umiarkowanym obciążeniu:The following chart display calling the preceding API with moderate load:

Poprzedni wykres

Na poprzednim wykresie kolekcje generacji 0 są wykonywane około raz na sekundę.In the preceding chart, generation 0 collections happen approximately once per second.

Poprzedni kod można zoptymalizować przez buforowanie byte buforu przy użyciu <T> ArrayPool.The preceding code can be optimized by pooling the byte buffer by using ArrayPool<T>. Wystąpienie statyczne jest ponownie używane między żądaniami.A static instance is reused across requests.

Różni się to od tego, czy obiekt w puli jest zwracany z interfejsu API.What's different with this approach is that a pooled object is returned from the API. Oznacza to:That means:

  • Obiekt jest poza kontrolką zaraz po powrocie z metody.The object is out of your control as soon as you return from the method.
  • Nie można zwolnić obiektu.You can't release the object.

Aby skonfigurować usuwanie obiektu:To set up disposal of the object:

RegisterForDispose zajmiemy się wywoływaniem Dispose obiektu docelowego, tak aby był on wydawany tylko po zakończeniu żądania HTTP.RegisterForDispose will take care of calling Disposeon the target object so that it's only released when the HTTP request is complete.

private static ArrayPool<byte> _arrayPool = ArrayPool<byte>.Create();

private class PooledArray : IDisposable
{
    public byte[] Array { get; private set; }

    public PooledArray(int size)
    {
        Array = _arrayPool.Rent(size);
    }

    public void Dispose()
    {
        _arrayPool.Return(Array);
    }
}

[HttpGet("pooledarray/{size}")]
public byte[] GetPooledArray(int size)
{
    var pooledArray = new PooledArray(size);

    var random = new Random();
    random.NextBytes(pooledArray.Array);

    HttpContext.Response.RegisterForDispose(pooledArray);

    return pooledArray.Array;
}

Zastosowanie tego samego obciążenia co wersja niebędąca w puli powoduje, że na poniższym wykresie:Applying the same load as the non-pooled version results in the following chart:

Poprzedni wykres

Główną różnicą jest przydzieloną liczbę bajtów, a jako wiele mniejszych kolekcji generacji 0.The main difference is allocated bytes, and as a consequence much fewer generation 0 collections.

Dodatkowe zasobyAdditional resources