Do httpClient và HttpClientHandler có phải được xử lý giữa các yêu cầu không?


333

System.Net.Http.HttpClientSystem.Net.Http.HttpClientHandler trong .NET Framework 4.5 triển khai IDis Dùng (thông qua System.Net.Http.HttpMessageInvoker ).

Các usingtài liệu tuyên bố nói:

Theo quy định, khi bạn sử dụng một đối tượng IDis Dùng một lần, bạn nên khai báo và khởi tạo nó trong một câu lệnh sử dụng.

Câu trả lời này sử dụng mẫu này:

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

Nhưng các ví dụ rõ ràng nhất từ ​​Microsoft không gọi Dispose()một cách rõ ràng hoặc ngầm định. Ví dụ:

Trong các bình luận của thông báo , có người đã hỏi nhân viên của Microsoft:

Sau khi kiểm tra các mẫu của bạn, tôi thấy rằng bạn đã không thực hiện hành động vứt bỏ trên ví dụ httpClient. Tôi đã sử dụng tất cả các phiên bản của HTTPClient bằng cách sử dụng câu lệnh trên ứng dụng của mình và tôi nghĩ rằng đó là cách đúng đắn vì HttpClient thực hiện giao diện IDis Dùng. Tôi có đang đi đúng hướng không?

Câu trả lời của anh là:

Nói chung là đúng mặc dù bạn phải cẩn thận với "bằng cách sử dụng" và không đồng bộ vì chúng không thực sự trộn lẫn trong .Net 4, Trong .Net 4.5, bạn có thể sử dụng "await" bên trong câu lệnh "bằng cách sử dụng".

Btw, bạn có thể sử dụng lại cùng một httpClient như nhiều lần [như] bạn thích vì vậy thông thường bạn sẽ không tạo / loại bỏ chúng mọi lúc.

Đoạn thứ hai là không cần thiết cho câu hỏi này, điều này không liên quan đến việc bạn có thể sử dụng một ví dụ httpClient bao nhiêu lần, nhưng về việc có cần thiết phải loại bỏ nó sau khi bạn không còn cần nó nữa.

(Cập nhật: trên thực tế, đoạn thứ hai là chìa khóa cho câu trả lời, như được cung cấp dưới đây bởi @DPeden.)

Vì vậy, câu hỏi của tôi là:

  1. Có cần thiết không, với việc triển khai hiện tại (.NET Framework 4.5), để gọi Dispose () trên các phiên bản httpClient và HttpClientHandler? Làm rõ: bằng "cần thiết" Ý tôi là nếu có bất kỳ hậu quả tiêu cực nào đối với việc không xử lý, chẳng hạn như rò rỉ tài nguyên hoặc rủi ro tham nhũng dữ liệu.

  2. Nếu không cần thiết, dù sao thì đó cũng là một "thông lệ tốt", vì họ đã triển khai IDis Dùng một lần?

  3. Nếu cần thiết (hoặc được khuyến nghị), mã này được đề cập ở trên có triển khai an toàn không (đối với .NET Framework 4.5)?

  4. Nếu các lớp này không yêu cầu gọi Dispose (), tại sao chúng được triển khai dưới dạng IDis Dùng?

  5. Nếu họ yêu cầu hoặc nếu đó là một thông lệ được khuyến nghị, thì các ví dụ của Microsoft có gây hiểu nhầm hoặc không an toàn không?


2
@Damien_The_Unbeliever, cảm ơn bạn đã phản hồi. Bạn có bất cứ đề nghị về cách tôi có thể làm rõ câu hỏi? Tôi muốn biết liệu nó có thể dẫn đến các vấn đề thường liên quan đến việc không xử lý tài nguyên, như rò rỉ tài nguyên và hỏng dữ liệu.
Fernando Correia

9
@Damien_The_Unbeliever: Không đúng. Cụ thể, các nhà văn stream phải được xử lý để có hành vi chính xác.
Stephen Cleary

1
@StephenCleary - bạn đang nghĩ đến khía cạnh nào? Chắc chắn, bạn có thể gọi Flushmột lần sau mỗi lần viết và ngoài sự bất tiện của việc tiếp tục giữ tài nguyên cơ bản lâu hơn mức cần thiết, điều gì sẽ không xảy ra cần thiết cho "hành vi đúng"?
Damien_The_Unbeliever

1
Điều này hoàn toàn sai: "Theo quy định, khi bạn sử dụng một đối tượng IDis Dùng một lần, bạn nên khai báo và khởi tạo nó trong một câu lệnh sử dụng". Tôi sẽ luôn đọc tài liệu về lớp triển khai IDis Dùng trước khi quyết định xem tôi có nên sử dụng nó không. Là tác giả của các thư viện nơi tôi triển khai IDis Dùng vì cần phải giải phóng các tài nguyên không bị thay đổi, tôi sẽ rất kinh hoàng nếu người tiêu dùng tạo ra một ví dụ mỗi lần thay vì sử dụng lại một cá thể hiện có. Điều đó không có nghĩa là cuối cùng đừng vứt bỏ ..
markmnl

1
Tôi đã gửi PR cho microsoft để cập nhật tài liệu của họ: github.com/dotnet/docs/pull/2470
markmnl

Câu trả lời:


258

Sự đồng thuận chung là bạn không (không nên) loại bỏ HTTPClient.

Nhiều người có liên quan mật thiết đến cách thức hoạt động đã tuyên bố điều này.

Xem bài đăng trên blog của Darrel Miller và một bài đăng SO có liên quan: Kết quả thu thập dữ liệu httpClient trong rò rỉ bộ nhớ để tham khảo.

Tôi cũng đề nghị bạn nên đọc chương HTTPClient từ Thiết kế API Web có thể phát triển với ASP.NET cho bối cảnh về những gì đang diễn ra dưới mui xe, đặc biệt là phần "Vòng đời" được trích dẫn ở đây:

Mặc dù HttpClient thực hiện gián tiếp giao diện IDis Dùng, nhưng việc sử dụng tiêu chuẩn của HTTPClient không phải là loại bỏ nó sau mỗi yêu cầu. Đối tượng HttpClient được dự định tồn tại miễn là ứng dụng của bạn cần thực hiện các yêu cầu HTTP. Việc có một đối tượng tồn tại trên nhiều yêu cầu cho phép một vị trí để đặt DefaultRequestHeaders và ngăn bạn khỏi phải chỉ định lại những thứ như CredentialCache và CookieContainer theo mọi yêu cầu cần thiết với HttpWebRequest.

Hoặc thậm chí mở DotPeek.


63
Để làm rõ câu trả lời của bạn, liệu có đúng không khi nói rằng "bạn không cần phải xử lý HTTPClient NẾU BẠN GIỮ LÊN TỨC THÌ ĐỂ SỬ DỤNG LATER IT"? Chẳng hạn, nếu một phương thức được gọi lặp đi lặp lại và tạo một cá thể HTTPClient mới (mặc dù đó không phải là mẫu được đề xuất trong hầu hết các trường hợp), liệu có đúng không khi nói phương thức này không nên loại bỏ cá thể đó (sẽ không được sử dụng lại)? Nó có thể dẫn đến hàng ngàn trường hợp không thể chối cãi. Nói cách khác, bạn nên thử và sử dụng lại các thể hiện, nhưng nếu bạn không sử dụng lại, bạn nên loại bỏ chúng (để giải phóng các kết nối)?
Fernando Correia

8
Tôi nghĩ rằng câu trả lời dễ hiểu nhưng câu trả lời đúng là nó phụ thuộc. Nếu tôi phải được ghim để đưa ra lời khuyên chung hoạt động trong hầu hết các trường hợp (tôi không bao giờ nói tất cả), tôi sẽ đề nghị bạn sử dụng bộ chứa IoC và đăng ký một ví dụ của httpClient dưới dạng đơn lẻ. Thời gian tồn tại của cá thể sau đó sẽ nằm trong phạm vi của thời gian tồn tại của container. Điều này có thể được giới hạn ở cấp ứng dụng hoặc có thể theo yêu cầu trong một ứng dụng web.
David Peden

25
@FernandoCorreia Có. Nếu vì một lý do nào đó, bạn liên tục tạo và hủy các thể hiện của HTTPClient thì có, bạn nên loại bỏ nó. Tôi không đề nghị bỏ qua giao diện IDis Dùng một lần, chỉ cố gắng khuyến khích mọi người sử dụng lại các trường hợp.
Darrel Miller

20
Chỉ để thêm uy tín cho câu trả lời này, tôi đã nói chuyện với nhóm HttpClient hôm nay và họ đã xác nhận rằng httpClient không được thiết kế để được sử dụng theo yêu cầu. Một phiên bản của HTTPClient nên được giữ nguyên trong khi ứng dụng khách tiếp tục tương tác với một máy chủ cụ thể.
Darrel Miller

19
@DavidPeden Đăng ký httpClient dưới dạng đơn lẻ nghe có vẻ nguy hiểm đối với tôi vì nó có thể thay đổi. Ví dụ, tất cả mọi người gán cho Timeouttài sản sẽ dẫm đạp lên nhau?
Jon-Eric

47

Các câu trả lời hiện tại hơi khó hiểu và gây hiểu lầm và chúng thiếu một số hàm ý DNS quan trọng. Tôi sẽ cố gắng tóm tắt nơi mọi thứ đứng rõ ràng.

  1. Nói chung, hầu hết IDisposablecác đối tượng nên được xử lý một cách lý tưởng khi bạn hoàn thành chúng , đặc biệt là những đối tượng sở hữu tài nguyên HĐH được đặt tên / chia sẻ . HttpClientcũng không ngoại lệ, vì như Darrel Miller chỉ ra rằng nó phân bổ các mã thông báo hủy và các cơ quan yêu cầu / phản hồi có thể là các luồng không được quản lý.
  2. Tuy nhiên, cách thực hành tốt nhất cho HttpClient nói rằng bạn nên tạo một cá thể và sử dụng lại nó càng nhiều càng tốt (sử dụng các thành viên an toàn luồng của nó trong các tình huống đa luồng). Do đó, trong hầu hết các kịch bản, bạn sẽ không bao giờ loại bỏ nó đơn giản vì bạn sẽ luôn cần nó .
  3. Vấn đề với việc sử dụng lại cùng một httpClient "mãi mãi" là kết nối HTTP cơ bản có thể vẫn mở đối với IP được phân giải DNS ban đầu, bất kể thay đổi DNS . Đây có thể là một vấn đề trong các tình huống như triển khai xanh / xanh và chuyển đổi dự phòng dựa trên DNS . Có nhiều cách tiếp cận khác nhau để xử lý vấn đề này, cách tiếp cận đáng tin cậy nhất liên quan đến máy chủ gửi Connection:closetiêu đề sau khi thay đổi DNS diễn ra. Một khả năng khác liên quan đến việc tái chế HttpClientphía khách hàng, theo định kỳ hoặc thông qua một số cơ chế tìm hiểu về thay đổi DNS. Xem https://github.com/dotnet/corefx/issues/11224 để biết thêm thông tin (Tôi khuyên bạn nên đọc kỹ trước khi sử dụng mã được đề xuất trong bài đăng trên blog được liên kết).

Tôi loại bỏ nó mọi lúc vì tôi không thể chuyển proxy trên một ví dụ;)
ed22

Nếu bạn cần phải xử lý HTTPClient vì bất kỳ lý do gì, bạn nên giữ một phiên bản tĩnh của HttpMessageHandler, vì cho rằng một trong những thực sự là nguyên nhân của các vấn đề được quy cho việc xử lý HTTPClient. HttpClient có quá tải hàm tạo cho phép bạn chỉ định rằng trình xử lý được cung cấp không nên bị loại bỏ, trong trường hợp đó bạn có thể sử dụng lại HTTPMessageHandler với các phiên bản httpClient khác.
Tom Lint

Bạn nên giữ httpClient của mình, nhưng bạn có thể sử dụng một cái gì đó như System.Net.ServicePointManager.DnsRefreshTimeout = 3000; Điều này rất hữu ích, ví dụ nếu bạn đang sử dụng thiết bị di động, bất cứ lúc nào cũng có thể chuyển đổi giữa wifi và 4G.
Johan Franzén

18

Theo hiểu biết của tôi, gọi Dispose() chỉ cần thiết khi nó khóa tài nguyên bạn cần sau này (như một kết nối cụ thể). Chúng tôi luôn khuyến nghị giải phóng tài nguyên mà bạn không còn sử dụng nữa, ngay cả khi bạn không cần sử dụng lại tài nguyên đó, đơn giản vì bạn thường không nên giữ tài nguyên mà bạn không sử dụng (ý định chơi chữ).

Ví dụ của Microsoft không phải là không chính xác. Tất cả các tài nguyên được sử dụng sẽ được phát hành khi ứng dụng thoát. Và trong trường hợp của ví dụ đó, điều đó xảy ra gần như ngay lập tức sau khi HttpClientsử dụng xong. Trong trường hợp như, gọi rõ ràng Dispose()là hơi thừa.

Nhưng, nói chung, khi một lớp thực hiện IDisposable , sự hiểu biết là bạn nên Dispose()có các thể hiện của nó ngay khi bạn hoàn toàn sẵn sàng và có thể. Tôi cho rằng điều này đặc biệt đúng trong các trường hợp như HttpClienttrong đó nó không được ghi chép rõ ràng về việc liệu các tài nguyên hoặc kết nối đang được giữ / mở. Trong trường hợp trong đó kết nối sẽ được sử dụng lại [sớm], bạn sẽ muốn từ bỏ Dipose()nó - bạn không "hoàn toàn sẵn sàng" trong trường hợp đó.

Xem thêm: IDisposable.Dispose Phương phápKhi nào thì gọi Dispose


7
Giống như nếu ai đó mang một quả chuối đến nhà bạn, ăn nó và đang đứng với vỏ. Họ nên làm gì với vỏ? ... Nếu họ đang trên đường ra khỏi cửa, hãy để họ đi. Nếu chúng dính xung quanh, hãy khiến chúng vứt nó vào thùng rác để nó không bị hôi thối.
Svidgen

chỉ để làm rõ câu trả lời này, bạn có nói, "không cần thiết phải loại bỏ nếu chương trình sẽ kết thúc ngay sau khi bạn sử dụng nó"? Và rằng bạn nên loại bỏ nếu chương trình dự kiến ​​sẽ tiếp tục trong một thời gian làm những việc khác?
Fernando Correia

@FernandoCorreia Vâng, trừ khi tôi quên điều gì đó, tôi nghĩ đó là một nguyên tắc an toàn. Hãy cho nó một số suy nghĩ trong từng trường hợp mặc dù. Ví dụ: nếu bạn đang làm việc với một kết nối, bạn không muốn Dispose()kết nối sớm và phải kết nối lại vài giây sau nếu kết nối hiện tại có thể sử dụng lại được. Tương tự như vậy, bạn không muốn Dispose()hình ảnh hoặc các cấu trúc khác một cách không cần thiết mà bạn có thể sẽ phải xây dựng lại trong một hoặc hai phút.
Svidgen

Tôi hiểu. Nhưng trong trường hợp cụ thể của HttpClient và HttpClientHandler mà câu hỏi này đề cập đến, liệu họ có đang giữ một tài nguyên mở như kết nối HTTP không? Nếu đó là những gì đang xảy ra, tôi có thể phải suy nghĩ lại về mô hình sử dụng chúng.
Fernando Correia

1
@DPeden Câu trả lời của bạn hoàn toàn không mâu thuẫn với tôi. Lưu ý, tôi đã nói, bạn nên loại bỏ () các trường hợp của nó ngay khi bạn hoàn toàn sẵn sàng và có thể . Nếu bạn có kế hoạch sử dụng lại ví dụ, bạn chưa sẵn sàng .
Svidgen

9

Vứt bỏ () gọi mã bên dưới, để đóng các kết nối được mở bởi thể hiện HTTPClient. Mã được tạo bằng cách dịch ngược với dotPeek.

HttpClientHandler.cs - Vứt bỏ

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

Nếu bạn không gọi xử lý thì ServicePointManager.MaxServicePointIdleTime, chạy bằng bộ đếm thời gian, sẽ đóng các kết nối http. Mặc định là 100 giây.

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

Nếu bạn không đặt thời gian nhàn rỗi thành vô hạn thì có vẻ an toàn không gọi vứt bỏ và để bộ hẹn giờ kết nối nhàn rỗi khởi động và đóng các kết nối cho bạn, mặc dù vậy sẽ tốt hơn nếu bạn gọi vứt bỏ trong câu lệnh sử dụng nếu bạn biết rằng bạn đã hoàn thành với một ví dụ httpClient và giải phóng tài nguyên nhanh hơn.


1
Bạn cũng có thể xem mã trên github. github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/
Kẻ

8

Câu trả lời ngắn: Không, tuyên bố trong câu trả lời hiện được chấp nhận là KHÔNG chính xác : "Sự đồng thuận chung là bạn không (không nên) không cần phải loại bỏ httpClient".

Câu trả lời dài : Cả hai câu sau đây đều đúng và có thể đạt được cùng một lúc:

  1. "HttpClient dự định sẽ được khởi tạo một lần và được sử dụng lại trong suốt vòng đời của một ứng dụng", trích dẫn từ tài liệu chính thức .
  2. Một IDisposableđối tượng được cho là / đề nghị được xử lý.

Và họ KHÔNG CẦN THIẾT BỊ CONFLICT với nhau. Đây chỉ là vấn đề về cách bạn tổ chức mã của mình để sử dụng lại HttpClientVÀ vẫn xử lý nó đúng cách.

Một câu trả lời thậm chí dài hơn trích dẫn từ câu trả lời khác của tôi :

Nó không phải là một trùng hợp ngẫu nhiên khi thấy mọi người trong một số bài đăng trên blog đổ lỗi như thế nào HttpClient's IDisposablegiao diện làm cho họ có xu hướng sử dụng các using (var client = new HttpClient()) {...}mô hình và sau đó dẫn đến vấn đề xử lý ổ cắm kiệt sức.

Tôi tin rằng điều đó dẫn đến một quan niệm bất thành văn (mis?): "Một đối tượng IDis Dùng được dự kiến ​​sẽ tồn tại trong thời gian ngắn" .

TUY NHIÊN, trong khi nó chắc chắn trông giống như một thứ tồn tại ngắn khi chúng ta viết mã theo phong cách này:

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

các tài liệu chính thức về IDisposable không bao giờ đề cập đếnIDisposable đối tượng phải ngắn ngủi. Theo định nghĩa, IDis Dùng chỉ là một cơ chế để cho phép bạn giải phóng các tài nguyên không được quản lý. Chỉ có bấy nhiêu thôi. Theo nghĩa đó, bạn được MỞ RỘNG để cuối cùng kích hoạt xử lý, nhưng nó không yêu cầu bạn phải làm như vậy trong một thời gian ngắn.

Do đó, công việc của bạn là chọn đúng thời điểm để kích hoạt xử lý, dựa trên yêu cầu vòng đời của đối tượng thực của bạn. Không có gì ngăn bạn sử dụng IDis Dùng một cách lâu dài:

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

Với cách hiểu mới này, bây giờ chúng tôi xem lại bài đăng trên blog đó , chúng tôi có thể nhận thấy rõ rằng "sửa chữa" khởi tạo HttpClientmột lần nhưng không bao giờ loại bỏ nó, đó là lý do tại sao chúng tôi có thể thấy từ đầu ra netstat của nó, kết nối vẫn ở trạng thái THÀNH LẬP có nghĩa là nó có KHÔNG được đóng đúng cách. Nếu nó bị đóng, thay vào đó, trạng thái của nó sẽ ở TIME_WAIT. Trong thực tế, sẽ không có vấn đề gì lớn khi chỉ rò rỉ một kết nối mở sau khi toàn bộ chương trình của bạn kết thúc và người đăng blog vẫn thấy hiệu suất tăng sau khi sửa lỗi; tuy nhiên, về mặt khái niệm không chính xác để đổ lỗi cho IDis Dùng và chọn KHÔNG loại bỏ nó.


cảm ơn bạn đã giải thích Nó rõ ràng làm sáng tỏ một số ánh sáng đối với sự đồng thuận. Từ ý kiến ​​của bạn, khi nào bạn nghĩ rằng nó là thích hợp để gọi HttpClient.Dispose?
Jeson Martajaya

@JesonMartajaya, loại bỏ nó khi ứng dụng của bạn không còn cần sử dụng ví dụ httpClient. Bạn có thể nghĩ đề xuất như vậy nghe có vẻ mơ hồ, nhưng trên thực tế, nó hoàn toàn có thể phù hợp với vòng đời của HttpClient clientbiến của bạn , đó là điều Lập trình-101 mà bạn có lẽ đang làm dù sao đi nữa. Bạn thậm chí có thể vẫn có thể sử dụng using (...) {...}. Ví dụ, xem mẫu Hello World bên trong câu trả lời của tôi.
RayLuo

7

Vì có vẻ như chưa có ai đề cập đến nó ở đây, nên cách tốt nhất mới để quản lý HttpClient và HttpClientHandler trong .Net Core 2.1 là sử dụng HttpClientFactory .

Nó giải quyết hầu hết các vấn đề đã nói ở trên và gotchas một cách sạch sẽ và dễ sử dụng. Từ bài viết tuyệt vời của Steve Gordon :

Thêm các gói sau vào dự án .Net Core (2.1.1 trở lên) của bạn:

Microsoft.AspNetCore.All
Microsoft.Extensions.Http

Thêm phần này vào Startup.cs:

services.AddHttpClient();

Tiêm và sử dụng:

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

Khám phá loạt bài viết trong blog của Steve để biết thêm nhiều tính năng.


4

Trong trường hợp của tôi, tôi đã tạo một HTTPClient bên trong một phương thức thực sự thực hiện cuộc gọi dịch vụ. Cái gì đó như:

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

Trong vai trò công nhân Azure, sau khi liên tục gọi phương thức này (mà không xử lý HTTPClient), cuối cùng nó sẽ thất bại với SocketException (lỗi kết nối không thành công).

Tôi đã biến HTTPClient thành một biến thể hiện (loại bỏ nó ở cấp độ lớp) và vấn đề đã biến mất. Vì vậy, tôi sẽ nói, vâng, loại bỏ HTTPClient, giả sử rằng nó an toàn (bạn không có các cuộc gọi async nổi bật) để làm như vậy.


Cảm ơn vì bạn đã phản hồi. Đây là một vấn đề hơi phức tạp. Tôi khuyên bạn nên đọc các bài viết được liên kết trong câu trả lời của DPeden. Nói tóm lại, cá thể httpClient nên được sử dụng lại trong suốt vòng đời của ứng dụng. Nếu bạn tạo các trường hợp mới nhiều lần, bạn có thể cần phải xử lý chúng.
Fernando Correia

6
"Ví dụ httpClient nên được sử dụng lại trong suốt vòng đời của ứng dụng" đây không phải là một ý tưởng hay với nhiều ứng dụng. Tôi đang nghĩ rằng các ứng dụng web sử dụng HttpClient. HttpClient giữ trạng thái (ví dụ như các tiêu đề yêu cầu mà nó sẽ sử dụng), vì vậy một luồng yêu cầu web có thể dễ dàng chà đạp những gì người khác đang làm. Trong các ứng dụng web lớn, tôi cũng đã thấy httpClient là vấn đề đối với các sự cố kết nối lớn. Khi nghi ngờ tôi nói Vứt bỏ.
bytedev

@nashwan bạn không thể xóa tiêu đề và thêm cái mới trước mỗi yêu cầu?
Mandeep Janjua

Microsoft cũng khuyên bạn nên sử dụng lại các phiên bản HTTPClient
Mandeep Janjua

@MandeepJanjua ví dụ đó dường như là một ứng dụng khách như một ứng dụng giao diện điều khiển. Tôi đã đề cập đến một ứng dụng web là khách hàng.
bytedev

3

Trong cách sử dụng thông thường (phản hồi <2GB), không cần thiết phải loại bỏ các HTTPResponseMessages.

Các kiểu trả về của các phương thức HttpClient nên được loại bỏ nếu Nội dung luồng của chúng không được đọc đầy đủ. Mặt khác, CLR không có cách nào để biết các Luồng đó có thể bị đóng cho đến khi chúng được thu gom rác.

  • Nếu bạn đang đọc dữ liệu thành một byte [] (ví dụ GetByteArrayAsync) hoặc chuỗi, tất cả dữ liệu đều được đọc, do đó không cần phải loại bỏ.
  • Các tình trạng quá tải khác sẽ mặc định để đọc Luồng lên tới 2GB (httpCompletionOption là FeedbackContentRead, HttpClient.MaxResponseContentBufferSize mặc định là 2GB)

Nếu bạn đặt HttpCompletionOption thành FeedbackHeadersRead hoặc phản hồi lớn hơn 2GB, bạn nên dọn sạch. Điều này có thể được thực hiện bằng cách gọi Vứt bỏ trên HttpResponseMessage hoặc bằng cách gọi Vứt bỏ / Đóng trên luồng thu được từ Nội dung httpResonseMessage hoặc bằng cách đọc hoàn toàn nội dung.

Việc bạn gọi Dispose trên HttpClient tùy thuộc vào việc bạn có muốn hủy các yêu cầu đang chờ xử lý hay không.


2

Nếu bạn muốn loại bỏ HTTPClient, bạn có thể nếu bạn thiết lập nó dưới dạng nhóm tài nguyên. Và vào cuối ứng dụng của bạn, bạn loại bỏ nguồn tài nguyên của bạn.

Mã số:

// 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 ("url cơ sở")).

  • HttpClient, như một giao diện, không thể gọi Dispose ().
  • Vứt bỏ () sẽ được gọi bởi Bộ thu gom rác. Hoặc khi chương trình dọn sạch đối tượng thông qua hàm hủy của nó.
  • Sử dụng các tài liệu tham khảo yếu + logic dọn dẹp bị trì hoãn để nó vẫn được sử dụng miễn là nó được sử dụng lại thường xuyên.
  • Nó chỉ phân bổ một httpClient mới cho mỗi URL cơ sở được truyền cho nó. Lý do giải thích bởi Ohad Schneider trả lời dưới đây. Hành vi xấu khi thay đổi url cơ sở.
  • HttpClientHandle cho phép Mocking trong các bài kiểm tra

Hoàn hảo. Tôi thấy rằng bạn gọi Disposephương thức mà bạn đăng ký trên GC. Điều này nên được đánh giá cao hơn trên đầu trang.
Jeson Martajaya

Lưu ý rằng HttpClient thực hiện tổng hợp tài nguyên trên mỗi URL cơ sở. Vì vậy, nếu bạn đang truy cập hàng ngàn trang web khác nhau trong một danh sách, hiệu suất của bạn sẽ giảm mà không làm sạch các trang web riêng lẻ đó. Điều này cho thấy khả năng loại bỏ từng url cơ sở. Tuy nhiên, nếu bạn chỉ sử dụng một trang web, có thể chỉ vì lý do học thuật để gọi xử lý.
TamusJRoyce

1

Sử dụng phép nội xạ phụ thuộc vào hàm tạo của bạn giúp việc quản lý vòng đời của bạn HttpClienttrở nên dễ dàng hơn - đưa trình quản lý trọn đời bên ngoài mã cần mã và làm cho nó dễ dàng thay đổi vào một ngày sau đó.

Sở thích hiện tại của tôi là tạo một lớp máy khách http riêng biệt kế thừa từ HttpClientmột lần cho mỗi tên miền điểm cuối đích và sau đó biến nó thành một đơn vị sử dụng phép nội xạ phụ thuộc.public class ExampleHttpClient : HttpClient { ... }

Sau đó, tôi có một phụ thuộc hàm tạo vào ứng dụng khách http tùy chỉnh trong các lớp dịch vụ nơi tôi cần truy cập vào API đó. Điều này giải quyết vấn đề trọn đời và có lợi thế khi kết nối nhóm.

Bạn có thể xem một ví dụ hoạt động trong câu trả lời có liên quan tại https://stackoverflow.com/a/50238944/3140853



-2

Tôi nghĩ rằng người ta nên sử dụng mẫu singleton để tránh phải tạo các thể hiện của HTTPClient và đóng nó mọi lúc. Nếu bạn đang sử dụng .Net 4.0, bạn có thể sử dụng mã mẫu như bên dưới. để biết thêm thông tin về mẫu singleton kiểm tra ở đây .

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

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

    private HttpClientSingletonWrapper()
    {
    }
}

Sử dụng mã như dưới đây.

var client = HttpClientSingletonWrapper.Instance;

3
Một cái gì đó để xem ra cho khi làm điều này (và các chương trình tương tự khác): " Bất kỳ thành viên dụ không đảm bảo được thread an toàn. "
tne

2
Câu trả lời này có chính xác hay không hoàn toàn nên phụ thuộc vào ứng dụng mà bạn muốn sử dụng HttpClient từ đâu. Nếu bạn có một ứng dụng web và tạo một httpClient đơn lẻ thì tất cả các yêu cầu web sẽ chia sẻ từ bạn có khả năng sẽ có nhiều ngoại lệ kết nối (tùy thuộc vào mức độ phổ biến của trang web của bạn! :-)). (Xem câu trả lời của David Faivre)
bytedev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.