HttpClient와 HttpClientHandler를 요청 사이에 배치해야합니까?


334

System.Net.Http.HttpClientSystem.Net.Http.HttpClientHandler.NET Framework 4.5의 는 IDisposable ( System.Net.Http.HttpMessageInvoker 를 통해 )을 구현 합니다.

그만큼 using문 문서는 말합니다 :

일반적으로 IDisposable 객체를 사용할 때는 using 문에서 선언하고 인스턴스화해야합니다.

이 답변 은 다음 패턴을 사용합니다.

var baseAddress = new Uri("http://example.com");
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
    var content = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("foo", "bar"),
        new KeyValuePair<string, string>("baz", "bazinga"),
    });
    cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
    var result = client.PostAsync("/test", content).Result;
    result.EnsureSuccessStatusCode();
}

그러나 Microsoft에서 가장 눈에 띄는 예제는 Dispose()명시 적 또는 암시 적으로 호출되지 않습니다 . 예를 들어 :

에서 발표 의 의견을이야, 누군가가 마이크로 소프트 직원에게 물었다 :

샘플을 확인한 후 HttpClient 인스턴스에서 처리 작업을 수행하지 않은 것으로 나타났습니다. 내 응용 프로그램에서 문을 사용하여 HttpClient의 모든 인스턴스를 사용했으며 HttpClient가 IDisposable 인터페이스를 구현하기 때문에 올바른 방법이라고 생각했습니다. 내가 올바른 길을 가고 있습니까?

그의 대답은 다음과 같습니다.

.Net 4에서 실제로 혼합되지 않으므로 "using"및 비동기에주의해야하지만 일반적으로 옳습니다. .Net 4.5에서는 "using"문에서 "await"를 사용할 수 있습니다.

Btw, 당신은 여러 번 같은 HttpClient를 재사용 할 수 있습니다. 일반적으로 당신은 그것들을 항상 생성 / 폐기하지 않을 것입니다.

두 번째 단락은 HttpClient 인스턴스를 몇 번 사용할 수 있는지에 대해 걱정하지 않고 더 이상 필요하지 않은 후에 폐기해야하는 경우에 관한 질문입니다.

(업데이트 : 사실 @DPeden이 제공 한 두 번째 단락이 답의 열쇠입니다.)

그래서 내 질문은 :

  1. 현재 구현 (.NET Framework 4.5)에서 HttpClient 및 HttpClientHandler 인스턴스에서 Dispose ()를 호출해야합니까? 설명 : "필수"란 리소스 누수 또는 데이터 손상 위험과 같이 폐기하지 않은 부정적인 결과가있는 경우를 의미합니다.

  2. 필요하지 않은 경우 IDisposable을 구현하기 때문에 어쨌든 "좋은 습관"이 될 것입니까?

  3. 필요하거나 권장되는 경우 위에서 언급 한이 코드 가 안전하게 구현됩니다 (.NET Framework 4.5의 경우)?

  4. 이러한 클래스가 Dispose ()를 호출 할 필요가없는 경우 왜 IDisposable로 구현 되었습니까?

  5. 필요한 경우 또는 권장되는 경우 Microsoft 예제가 오도되거나 안전하지 않습니까?


2
@Damien_The_Unbeliever, 의견 주셔서 감사합니다. 질문을 명확히하는 방법에 대한 제안이 있습니까? 리소스 누수 및 데이터 손상과 같은 리소스를 폐기하지 않는 것과 관련된 문제로 이어질 수 있는지 알고 싶습니다.
Fernando Correia

9
@Damien_The_Unbeliever : 사실이 아닙니다. 특히 스트림 기록기는 올바른 동작을 갖도록 배치해야합니다.
Stephen Cleary

1
@StephenCleary-당신은 어떤 측면을 생각하고 있습니까? 확실히, 당신은 Flush매번 글을 쓸 때마다 하나씩 전화 를 걸 수 있으며 , 그것이 필요한 것보다 오랫동안 기본 자원을 계속 보유하는 불편 함 외에 "올바른 행동"에 필요하지 않은 것은 무엇입니까?
Damien_The_Unbeliever

1
"일반적으로 IDisposable 객체를 사용할 때는 using 문에서 선언하고 인스턴스화해야합니다." IDisposable을 구현하는 클래스의 설명서를 읽고 사용 여부를 결정하기 전에 항상 읽을 것입니다. IDisposable becuase를 구현하는 라이브러리의 작성자가 관리되지 않는 리소스를 해제해야하므로 소비자가 기존 인스턴스를 재사용하는 대신 매번 인스턴스를 생성하면 충격을받습니다. 그것은 결국 인스턴스를 폐기하지 말라고 말하는 것이 아닙니다.
markmnl

1
문서를 업데이트하기 위해 Microsoft에 PR을 제출했습니다. github.com/dotnet/docs/pull/2470
markmnl

답변:


259

일반적인 합의는 HttpClient를 폐기 할 필요가 없다는 것입니다.

그것이 작동하는 방식에 밀접하게 관여하는 많은 사람들이 이것을 언급했습니다.

참조 대럴 밀러의 블로그 게시물 과 관련 SO 게시물 : HttpClient를이 메모리 누수의 결과를 크롤링 참조.

또한 ASP.NET 을 사용 하여 Evolvable Web APIs 디자인 의 HttpClient 장 을 읽고 여기에서 인용되는 "라이프 사이클"섹션에 대한 내용을 읽어 볼 것을 강력히 제안합니다 .

HttpClient는 IDisposable 인터페이스를 간접적으로 구현하지만 HttpClient의 표준 사용법은 모든 요청 후에이를 폐기하지 않습니다. HttpClient 객체는 애플리케이션이 HTTP 요청을해야하는 한 지속되도록 설계되었습니다. 여러 요청에 객체가 있으면 DefaultRequestHeaders를 설정할 수 있으며 HttpWebRequest에 필요한 모든 요청에서 CredentialCache 및 CookieContainer와 같은 항목을 다시 지정하지 않아도됩니다.

또는 DotPeek을 열 수도 있습니다.


64
답을 명확하게하기 위해 "나중에 재사용하기 위해 INSTANCE를 보유하고 있다면 HttpClient를 폐기 할 필요가 없다"고 말하는 것이 옳습니까? 예를 들어, 메소드가 반복적으로 호출되어 새로운 HttpClient 인스턴스를 작성하는 경우 (대부분의 경우 권장되는 패턴은 아니지만)이 메소드가 인스턴스를 폐기해서는 안된다고 말하는 것이 여전히 옳습니까 (재사용되지 않음)? 수천 개의 처리되지 않은 인스턴스로 이어질 수 있습니다. 다시 말해, 인스턴스를 재사용하고 다시 시도해야하지만 재사용하지 않으면 인스턴스를 폐기하는 것이 더 좋습니다 (연결 해제)?
Fernando Correia

8
나는 이해하기 쉽지만 정답은 그것이 달려 있다고 생각합니다. 대부분의 경우에 효과가있는 일반적인 조언을 제공 해야하는 경우 IoC 컨테이너를 사용하고 HttpClient 인스턴스를 싱글 톤으로 등록하는 것이 좋습니다. 그러면 인스턴스의 수명은 컨테이너 수명의 범위로 결정됩니다. 이것은 응용 프로그램 수준에서 또는 웹 응용 프로그램의 요청별로 범위를 지정할 수 있습니다.
David Peden

25
@FernandoCorreia 네. 어떤 이유로 든 HttpClient 인스턴스를 반복적으로 생성하고 삭제하면 예, 처리해야합니다. IDisposable 인터페이스를 무시하지 말고 사람들이 인스턴스를 재사용하도록 독려하려고합니다.
Darrel Miller

20
이 답변에 신뢰를 더하기 위해 오늘 HttpClient 팀과 대화를 나으며 HttpClient가 요청 당 사용되도록 설계되지 않았 음을 확인했습니다. 클라이언트 응용 프로그램이 특정 호스트와 계속 상호 작용하는 동안 HttpClient의 인스턴스는 활성 상태로 유지되어야합니다.
Darrel Miller

19
@DavidPeden HttpClient를 싱글 톤으로 등록하면 변경할 수 있기 때문에 위험합니다. 예를 들어, Timeout부동산에 배정 된 모든 사람이 서로 마주 치지 않습니까?
Jon-Eric

47

현재 답변은 약간 혼란스럽고 오해의 소지가 있으며 중요한 DNS 영향이 누락되었습니다. 나는 상황이 분명하게 어디에 있는지 요약하려고 노력할 것입니다.

  1. 일반적으로 말하면 대부분의 IDisposable객체 , 특히 명명 된 / 공유 OS 리소스소유 한 객체 를 처리 할 때는 이상적으로 처리해야 합니다 . Darrel MillerHttpClient 로서 지적했듯이 취소 토큰을 할당하고 요청 / 응답 본문이 관리되지 않는 스트림 일 수 .
  2. 그러나 HttpClient모범 사례에 따르면 인스턴스를 하나 만들고 최대한 많이 재사용해야합니다 ( 다중 스레드 시나리오에서 스레드 안전 멤버 사용 ). 따라서 대부분의 시나리오에서는 항상 필요할 것이므로 단순히 폐기하지 않습니다 .
  3. 동일한 HttpClient를 "영원히"재사용 할 때의 문제점 은 기본 HTTP 연결이 DNS 변경에 관계없이 원래 DNS 확인 IP에 대해 열려있을 수 있다는 것 입니다. 이는 블루 / 그린 배포 및 DNS 기반 장애 조치와 같은 시나리오에서 문제가 될 수 있습니다 . 이 문제를 처리하기위한 다양한 접근 방법이 Connection:close있습니다. DNS 변경이 발생한 후 서버가 헤더를 전송하는 가장 안정적인 방법 입니다. 또 다른 가능성은 HttpClient클라이언트 측에서 주기적으로 또는 DNS 변경에 대해 학습하는 메커니즘을 통해 클라이언트 를 재활용하는 것입니다. 자세한 내용은 https://github.com/dotnet/corefx/issues/11224 를 참조하십시오 (링크 된 블로그 게시물에서 제안 된 코드를 맹목적으로 사용하기 전에주의 깊게 읽으십시오).

인스턴스에서 프록시를 전환 할 수 없으므로 항상 처리합니다.)
ed22

어떤 이유로 든 HttpClient를 폐기 해야하는 경우 HttpMessageHandler의 정적 인스턴스를 HttpClient 폐기로 인한 문제의 원인으로 처리해야합니다. HttpClient에는 제공된 처리기를 폐기하지 않도록 지정할 수있는 생성자 오버로드가 있습니다.이 경우 HttpMessageHandler를 다른 HttpClient 인스턴스와 함께 재사용 할 수 있습니다.
Tom Lint

HttpClient를 유지해야하지만 System.Net.ServicePointManager.DnsRefreshTimeout = 3000; 예를 들어 Wi-Fi와 4G 간을 전환 할 수있는 모바일 장치를 사용하는 경우에 유용합니다.
Johan Franzén

18

필자는 이해하기 위해 Dispose()나중에 필요한 리소스를 잠글 때만 호출 이 필요합니다 (특정 연결처럼). 더 이상 필요하지 않더라도 더 이상 사용하지 않는 리소스는 항상 사용하지 않는 것이 좋습니다. 일반적으로 사용하지 않는 리소스를 사용하지 않아야하기 때문입니다.

Microsoft 예제가 반드시 정확하지는 않습니다. 응용 프로그램이 종료되면 사용 된 모든 리소스가 해제됩니다. 그리고이 예제의 경우, HttpClient사용이 완료된 직후에 발생합니다 . 마찬가지로 명시 적으로 전화하는 Dispose()것은 다소 불필요한 것입니다.

그러나 일반적으로 클래스가 구현할 때는 완전히 준비되고 가능한 한 빨리 인스턴스를 IDisposable이해해야합니다 Dispose(). HttpClient리소스 나 연결이 유지 / 개방되는지 여부에 대해 명시 적으로 문서화되어 있지 않은 경우에 특히 그렇습니다 . 연결이 다시 재사용되는 경우 [곧]Dipose() 것입니다.이 경우 "완전히 준비되지 않았습니다".

참조 : IDisposable.Dispose 메서드Dispose 호출시기


7
마치 누군가가 바나나를 집에 가져 와서 먹고 껍질을 벗기고 서있는 것과 같습니다. 껍질로 무엇을해야합니까? ... 그들이 문 밖으로 나가는 중이라면 놓아주십시오. 그들이 붙어 있으면 쓰레기를 쓰레기통에 버려서 장소를 방해하지 않도록하십시오.
svidgen

이 대답을 명확하게하기 위해 "프로그램을 사용한 직후에 프로그램이 종료 될 경우 폐기 할 필요가 없습니다"라고 말하고 있습니까? 그리고 프로그램이 다른 일을 한동안 계속할 것으로 예상된다면 처분해야합니까?
Fernando Correia

@FernandoCorreia 그렇습니다. 무언가를 잊어 버리지 않는다면 안전한 원리라고 생각합니다. 그래도 각 경우에 대해 생각하십시오. 예를 들어, 연결 작업을하는 경우 연결 Dispose()을 조기에 원하지 않고 기존 연결을 재사용 할 수 있는 경우 몇 초 후에 다시 연결해야합니다 . 마찬가지로, 불필요하게 Dispose()이미지 나 다른 구조를 원하지 않으면 1 ~ 2 분 안에 다시 빌드해야 할 수도 있습니다.
svidgen

이해 했어요. 그러나이 질문에 관한 HttpClient 및 HttpClientHandler의 특정 경우에는 HTTP 연결과 같은 열린 자원을 보유하고 있습니까? 그것이 일어나고 있다면, 그것들을 사용하는 패턴을 다시 생각해야 할 수도 있습니다.
Fernando Correia

1
@DPeden 귀하의 답변이 전혀 저와 충돌하지 않습니다. 참고, 나는 말했다, 당신은 즉시 당신이있는 한 ()의 인스턴스를 폐기해야 완벽하게 준비 할 수 . 인스턴스를 다시 사용하려는 경우 준비 가되지 않은 것 입니다.
svidgen

9

Dispose ()는 아래 코드를 호출하여 HttpClient 인스턴스에서 연 연결을 닫습니다. 이 코드는 dotPeek으로 디 컴파일하여 작성되었습니다.

HttpClientHandler.cs-폐기

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

dispose를 호출하지 않으면 타이머로 실행되는 ServicePointManager.MaxServicePointIdleTime이 http 연결을 닫습니다. 기본값은 100 초입니다.

ServicePointManager.cs

internal static readonly TimerThread.Callback s_IdleServicePointTimeoutDelegate = new TimerThread.Callback(ServicePointManager.IdleServicePointTimeoutCallback);
private static volatile TimerThread.Queue s_ServicePointIdlingQueue = TimerThread.GetOrCreateQueue(100000);

private static void IdleServicePointTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)
{
  ServicePoint servicePoint = (ServicePoint) context;
  if (Logging.On)
    Logging.PrintInfo(Logging.Web, SR.GetString("net_log_closed_idle", (object) "ServicePoint", (object) servicePoint.GetHashCode()));
  lock (ServicePointManager.s_ServicePointTable)
    ServicePointManager.s_ServicePointTable.Remove((object) servicePoint.LookupString);
  servicePoint.ReleaseAllConnectionGroups();
}

유휴 시간을 무한으로 설정하지 않은 경우 dispose를 호출하지 않고 유휴 연결 타이머를 시작하고 연결을 닫는 것이 안전하지만 using 문에서 dispose를 호출하는 것이 좋습니다. HttpClient 인스턴스를 사용하여 리소스를 더 빨리 확보 할 수 있습니다.


1
github에서 코드를 볼 수도 있습니다. github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/…
TamusJRoyce

8

짧은 답변 : 아니요. 현재 승인 된 답변의 내용이 정확하지 않습니다. . "일반적인 의견은"HttpClient를 폐기 할 필요가 없다는 것입니다. "

긴 대답 : 다음 진술 모두 사실이며 동시에 달성 가능합니다.

  1. 공식 문서 에서 인용 한 "HttpClient는 한 번 인스턴스화되어 응용 프로그램 수명 동안 재사용됩니다" ."
  2. IDisposable개체를 배치 할 것을 권장합니다 / 가정된다.

그리고 그들은 반드시 서로 충돌하지 않습니다. 재사용을 위해 코드를 구성하는 방법에 관한 문제입니다.HttpClientAND 하고 올바르게 처리 .

다른 답변 에서 인용 한 더 답변 :

사람들을 볼 수있는 우연이 아닌 일부 블로그 게시물 어떻게 비난 HttpClientIDisposable그들을를 사용하는 경향이 인터페이스를 만든다using (var client = new HttpClient()) {...} 패턴을 다음 소진 소켓 핸들러 문제로 이어집니다.

나는 그 무언으로 내려 오는 생각의 개념을 (잘못?) 의 "는 IDisposable 개체가 단명 할 것으로 예상된다" .

그러나이 스타일로 코드를 작성할 때 확실히 수명이 짧은 것처럼 보입니다.

using (var foo = new SomeDisposableObject())
{
    ...
}

IDisposable에 대한 공식 문서IDisposable객체의 수명이 짧아야 한다고 언급하지 않습니다 . 정의에 따르면 IDisposable은 관리되지 않는 리소스를 해제 할 수있는 메커니즘 일뿐입니다. 더 이상 없습니다. 그런 의미에서 결국 폐기를 촉발 할 것으로 예상되지만 단기적으로 그렇게 할 필요는 없습니다.

따라서 실제 물체의 수명주기 요구 사항에 따라 폐기시기를 적절하게 선택하는 것이 귀하의 임무입니다. IDisposable을 오래 사용하는 것을 막을 수있는 것은 없습니다 :

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");

            using (var client = new HttpClient())
            {
                for (...) { ... }  // A really long loop

                // Or you may even somehow start a daemon here

            }

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

이 새로운 이해를 통해 이제 블로그 게시물 을 다시 방문 하면 "수정"이 HttpClient한 번 초기화 되지만 절대 폐기하지 않는다는 것을 분명히 알 수 있습니다. 따라서 netstat 출력에서 ​​연결이 ESTABLISHED 상태로 유지된다는 것을 알 수 있습니다. 제대로 닫히지 않았습니다. 닫히면 상태는 TIME_WAIT입니다. 실제로 전체 프로그램이 종료 된 후 하나의 연결 만 열면 크게 문제가되지 않으며 블로그 포스터는 여전히 수정 후에도 성능이 향상됩니다. 그러나 여전히 IDisposable을 비난하고 처분하지 않기로 선택하는 것은 개념적으로 올바르지 않습니다.


이 설명에 감사드립니다. 그것은 합의를 향한 약간의 빛을 분명하게 비춘다. 귀하의 의견으로는 언제 전화 하는 것이 적절 하다고 생각 HttpClient.Dispose하십니까?.
Jeson Martajaya '17 년

@JesonMartajaya는 애플리케이션이 더 이상 httpClient 인스턴스를 사용할 필요가 없을 때 폐기합니다. 그러한 제안이 모호하다고 생각할 수도 있지만 실제로 HttpClient client는 변수 의 수명주기와 완벽하게 일치 할 수 있습니다 . 이는 아마도 이미 어쨌든 이미 수행중인 Programming-101입니다. 여전히 사용할 수도 using (...) {...}있습니다. 예를 들어, 내 답변 안의 Hello World 샘플을 참조하십시오.
RayLuo

7

아직 아무도 언급하지 않은 것 같으므로 .Net Core 2.1에서 HttpClient 및 HttpClientHandler를 관리하는 새로운 최선의 방법은 HttpClientFactory를 사용하는 것입니다. 입니다.

앞에서 언급 한 대부분의 문제와 문제를 깨끗하고 사용하기 쉬운 방식으로 해결합니다. 에서 스티브 고든의 위대한 블로그 게시물 :

.Net Core (2.1.1 이상) 프로젝트에 다음 패키지를 추가하십시오.

Microsoft.AspNetCore.All
Microsoft.Extensions.Http

이것을 Startup.cs에 추가하십시오.

services.AddHttpClient();

주사 및 사용 :

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient();
        var result = await client.GetStringAsync("http://www.google.com");
        return Ok(result);
    }
}

더 많은 기능에 대해서는 Steve의 블로그에서 일련의 게시물을 살펴보십시오.


4

필자의 경우 실제로 서비스 호출을 수행 한 메소드 내에 HttpClient를 작성 중이었습니다. 다음과 같은 것 :

public void DoServiceCall() {
  var client = new HttpClient();
  await client.PostAsync();
}

Azure 작업자 역할에서 HttpClient를 삭제하지 않고이 메서드를 반복적으로 호출하면 결국 실패합니다. SocketException (연결 시도 실패).

HttpClient를 인스턴스 변수로 만들고 (클래스 수준에서 배치) 문제가 사라졌습니다. 그래서, 안전하다고 가정하면 (당신은 뛰어난 비동기 호출이 없다고 가정) HttpClient를 처분하십시오.


피드백 감사드립니다. 이것은 다소 복잡한 문제입니다. DPeden의 답변에 링크 된 기사를 읽는 것이 좋습니다. 간단히 말해 HttpClient 인스턴스는 응용 프로그램 수명주기 동안 재사용해야합니다. 새 인스턴스를 반복해서 생성하면 폐기해야 할 수도 있습니다.
Fernando Correia 2016 년

6
"HttpClient 인스턴스는 응용 프로그램 수명주기 내내 재사용해야합니다"이것은 많은 응용 프로그램에서 좋은 생각이 아닙니다. HttpClient를 사용하는 웹 응용 프로그램을 생각하고 있습니다. HttpClient는 상태 (예 : 사용할 요청 헤더)를 유지하므로 한 웹 요청 스레드가 다른 웹 요청 스레드를 쉽게 수행 할 수 있습니다. 대규모 웹 응용 프로그램에서 HttpClient를 주요 연결 문제의 문제로 보았습니다. 의심 스러우면 Dispose라고 말합니다.
bytedev

@nashwan 당신은 각 요청 전에 헤더를 지우고 새로운 헤더를 추가 할 수 없습니까?
Mandeep Janjua

Microsoft는 또한 HttpClient 인스턴스를 재사용 할 것을 권장합니다 -docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/…
Mandeep Janjua

@MandeepJanjua이 예제는 콘솔 응용 프로그램으로 클라이언트로 보입니다. 나는 클라이언트 인 웹 응용 프로그램을 언급하고있었습니다.
bytedev

3

일반적인 사용법 (응답 <2GB)에서는 HttpResponseMessages를 폐기 할 필요가 없습니다.

HttpClient 메소드의 리턴 유형은 스트림 컨텐츠가 완전히 읽히지 않으면 처리되어야합니다. 그렇지 않으면 CLR이 해당 스트림이 가비지 수집 될 때까지 닫힐 수 있음을 알 수있는 방법이 없습니다.

  • 바이트 [] (예 : GetByteArrayAsync) 또는 문자열로 데이터를 읽는 경우 모든 데이터를 읽으므로 처리 할 필요가 없습니다.
  • 다른 과부하는 기본적으로 스트림을 최대 2GB까지 읽습니다 (HttpCompletionOption은 ResponseContentRead, HttpClient.MaxResponseContentBufferSize 기본값은 2GB 임)

HttpCompletionOption을 ResponseHeadersRead로 설정하거나 응답이 2GB보다 큰 경우 정리해야합니다. HttpResponseMessage에서 Dispose를 호출하거나 HttpResonseMessage Content에서 얻은 스트림에서 Dispose / Close를 호출하거나 내용을 완전히 읽어서 수행 할 수 있습니다.

HttpClient에서 Dispose를 호출하는지 여부는 보류중인 요청을 취소할지 여부에 따라 다릅니다.


2

HttpClient를 삭제하려면 리소스 풀로 설정하면됩니다. 응용 프로그램이 끝나면 리소스 풀을 폐기합니다.

암호:

// Notice that IDisposable is not implemented here!
public interface HttpClientHandle
{
    HttpRequestHeaders DefaultRequestHeaders { get; }
    Uri BaseAddress { get; set; }
    // ...
    // All the other methods from peeking at HttpClient
}

public class HttpClientHander : HttpClient, HttpClientHandle, IDisposable
{
    public static ConditionalWeakTable<Uri, HttpClientHander> _httpClientsPool;
    public static HashSet<Uri> _uris;

    static HttpClientHander()
    {
        _httpClientsPool = new ConditionalWeakTable<Uri, HttpClientHander>();
        _uris = new HashSet<Uri>();
        SetupGlobalPoolFinalizer();
    }

    private DateTime _delayFinalization = DateTime.MinValue;
    private bool _isDisposed = false;

    public static HttpClientHandle GetHttpClientHandle(Uri baseUrl)
    {
        HttpClientHander httpClient = _httpClientsPool.GetOrCreateValue(baseUrl);
        _uris.Add(baseUrl);
        httpClient._delayFinalization = DateTime.MinValue;
        httpClient.BaseAddress = baseUrl;

        return httpClient;
    }

    void IDisposable.Dispose()
    {
        _isDisposed = true;
        GC.SuppressFinalize(this);

        base.Dispose();
    }

    ~HttpClientHander()
    {
        if (_delayFinalization == DateTime.MinValue)
            _delayFinalization = DateTime.UtcNow;
        if (DateTime.UtcNow.Subtract(_delayFinalization) < base.Timeout)
            GC.ReRegisterForFinalize(this);
    }

    private static void SetupGlobalPoolFinalizer()
    {
        AppDomain.CurrentDomain.ProcessExit +=
            (sender, eventArgs) => { FinalizeGlobalPool(); };
    }

    private static void FinalizeGlobalPool()
    {
        foreach (var key in _uris)
        {
            HttpClientHander value = null;
            if (_httpClientsPool.TryGetValue(key, out value))
                try { value.Dispose(); } catch { }
        }

        _uris.Clear();
        _httpClientsPool = null;
    }
}

var handler = HttpClientHander.GetHttpClientHandle (new Uri ( "base url")).

  • 인터페이스 인 HttpClient는 Dispose ()를 호출 할 수 없습니다.
  • Dispose ()는 가비지 수집기에서 지연된 방식으로 호출됩니다. 또는 프로그램이 소멸자를 통해 개체를 정리할 때.
  • 약한 참조 + 지연 정리 로직을 사용하므로 자주 재사용되는 한 계속 사용됩니다.
  • 전달 된 각 기본 URL에 대해서만 새 HttpClient를 할당합니다. Ohad Schneider가 설명하는 이유는 다음과 같습니다. 기본 URL을 변경할 때 잘못된 동작입니다.
  • HttpClientHandle은 테스트에서 모의를 허용합니다.

완전한. DisposeGC에 등록한 메소드 를 호출 하는 것을 알 수 있습니다. 상단에서 더 높은 등급을 받아야합니다.
Jeson Martajaya

HttpClient는 기본 URL마다 리소스 풀링을 수행합니다. 따라서 목록에서 수천 개의 다른 웹 사이트를 방문하는 경우 개별 사이트를 정리하지 않고 성능이 저하됩니다. 이렇게하면 각 기본 URL을 처리 할 수 ​​있습니다. 그러나 하나의 웹 사이트 만 사용하는 경우 학문적 이유로 만 dispose를 호출해야 할 수 있습니다.
TamusJRoyce

1

생성자에 의존성 주입을 사용하면 수명을 HttpClient쉽게 관리 할 수 ​​있습니다. 수명 코드를 필요로하는 코드 외부 에서 수명을 관리하고 나중에 쉽게 변경할 수 있습니다.

현재 선호하는 것은 대상 엔드 포인트 도메인 당 한 번 부터 상속되는 별도의 HTTP 클라이언트 클래스HttpClient작성하고 종속성 주입을 사용하여 단일 HTTP 클래스 를 만드는 것입니다.public class ExampleHttpClient : HttpClient { ... }

그런 다음 해당 API에 액세스해야하는 서비스 클래스에서 사용자 정의 http 클라이언트에 대한 생성자 종속성을 가져옵니다. 이는 수명 문제를 해결하고 연결 풀링에있어 이점이 있습니다.

https://stackoverflow.com/a/50238944/3140853 에서 관련 답변의 작동 예를 볼 수 있습니다.



-2

HttpClient의 인스턴스를 만들고 항상 닫지 않아도되도록 싱글 톤 패턴을 사용해야한다고 생각합니다. .Net 4.0을 사용하는 경우 아래와 같이 샘플 코드를 사용할 수 있습니다. 싱글 톤 패턴 검사에 대한 자세한 내용은 여기를 참조하십시오 .

class HttpClientSingletonWrapper : HttpClient
{
    private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper()); 

    public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}

    private HttpClientSingletonWrapper()
    {
    }
}

아래와 같이 코드를 사용하십시오.

var client = HttpClientSingletonWrapper.Instance;

3
이를 수행 할 때주의해야 할 사항 (및 기타 유사한 구성표) : " 인스턴스 멤버는 스레드 안전을 보장 할 수 없습니다. "
tne

2
이 답변이 맞는지 아닌지는 전적으로 HttpClient를 사용하려는 응용 프로그램에 따라 달라집니다. 웹 응용 프로그램이 있고 모든 웹 요청이 공유 할 단일 HttpClient를 만들면 잠재적으로 많은 연결 예외가 발생합니다 (웹 사이트의 인기도에 따라! :-)). (David Faivre의 답변 참조)
bytedev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.