HttpClient และ HttpClientHandler ต้องถูกกำจัดระหว่างการร้องขอหรือไม่?


334

System.Net.Http.HttpClientและSystem.Net.Http.HttpClientHandlerใน. NET Framework 4.5 ใช้ IDisposable (ผ่านSystem.Net.Http.HttpMessageInvoker )

usingเอกสารคำสั่งพูดว่า:

ตามกฎแล้วเมื่อคุณใช้วัตถุ IDisposable คุณควรประกาศและสร้างอินสแตนซ์ในข้อความสั่งการใช้

คำตอบนี้ใช้รูปแบบนี้:

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()อย่างชัดเจนหรือโดยปริยาย ตัวอย่างเช่น

ในความคิดเห็นของประกาศมีคนถามพนักงานของ Microsoft:

หลังจากตรวจสอบตัวอย่างของคุณฉันเห็นว่าคุณไม่ได้ทำการดำเนินการจัดการกับอินสแตนซ์ HttpClient ฉันใช้ HttpClient ทุกอินสแตนซ์กับการใช้คำสั่งบนแอปของฉันและฉันคิดว่ามันเป็นวิธีที่ถูกต้องเนื่องจาก HttpClient ใช้อินเทอร์เฟซ IDisposable ฉันอยู่บนเส้นทางที่ถูกต้องหรือไม่?

คำตอบของเขาคือ:

โดยทั่วไปนั้นถูกต้องแม้ว่าคุณจะต้องระวังด้วย "การใช้" และ async เพราะพวกเขาไม่ได้ 'ผสมใน. สุทธิ 4, ใน. Net 4.5 คุณสามารถใช้ "คอย" ภายในคำสั่ง "การใช้"

แต่คุณสามารถนำ HttpClient มาใช้ซ้ำได้บ่อยเท่าที่คุณต้องการโดยทั่วไปแล้วคุณจะไม่สร้าง / กำจัดพวกเขาตลอดเวลา

ย่อหน้าที่สองไม่จำเป็นสำหรับคำถามนี้ซึ่งไม่เกี่ยวข้องกับจำนวนครั้งที่คุณสามารถใช้อินสแตนซ์ HttpClient แต่ถ้าจำเป็นต้องกำจัดมันหลังจากที่คุณไม่ต้องการมันอีกต่อไป

(อัปเดต: ในความจริงแล้วย่อหน้าที่สองเป็นกุญแจสำคัญของคำตอบตามที่ระบุไว้ด้านล่างโดย @DPeden)

ดังนั้นคำถามของฉันคือ:

  1. จำเป็นหรือไม่เนื่องจากการใช้งานปัจจุบัน (.NET Framework 4.5) เพื่อเรียก Dispose () บนอินสแตนซ์ HttpClient และ HttpClientHandler การชี้แจง: โดย "จำเป็น" ฉันหมายถึงหากมีผลกระทบด้านลบใด ๆ จากการไม่จำหน่ายเช่นการรั่วไหลของทรัพยากรหรือความเสี่ยงต่อการเสียหายของข้อมูล

  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 คุณควรประกาศและสร้างอินสแตนซ์ของมันในการใช้คำสั่ง" ฉันจะอ่านเอกสารเกี่ยวกับการใช้ IDisposable ในชั้นเรียนเสมอก่อนตัดสินใจว่าควรจะใช้กับมันหรือไม่ ในฐานะผู้เขียนห้องสมุดที่ฉันใช้งาน IDisposable เพราะจำเป็นต้องปล่อยทรัพยากรที่ไม่มีการเปลี่ยนแปลงฉันจะตกใจถ้าผู้บริโภคสร้างอินสแตนซ์ในแต่ละครั้งแทนที่จะใช้อินสแตนซ์ที่มีอยู่อีกครั้ง ไม่ได้บอกว่าอย่ากำจัดตัวอย่างในที่สุด ..
markmnl

1
ฉันได้ส่ง PR ไปยังไมโครซอฟท์เพื่ออัปเดตเอกสารของพวกเขา: github.com/dotnet/docs/pull/2470
markmnl

คำตอบ:


259

ฉันทามติทั่วไปคือคุณไม่จำเป็นต้องกำจัด HttpClient

หลายคนที่เกี่ยวข้องอย่างใกล้ชิดในวิธีการทำงานได้ระบุไว้นี้

ดูโพสต์บล็อกของ Darrel Millerและโพสต์ SO ที่เกี่ยวข้อง: HttpClient การรวบรวมข้อมูลส่งผลให้หน่วยความจำรั่วไหลสำหรับการอ้างอิง

ฉันขอแนะนำอย่างยิ่งให้คุณอ่านบท HttpClient จากการออกแบบ Evolvable Web APIs ที่มี ASP.NETสำหรับบริบทว่าเกิดอะไรขึ้นภายใต้ประทุนโดยเฉพาะส่วน "Lifecycle" ที่ยกมาที่นี่:

แม้ว่า HttpClient จะใช้อินเทอร์เฟซ IDisposable ทางอ้อมการใช้มาตรฐานของ HttpClient จะไม่ทิ้งหลังจากการร้องขอทุกครั้ง วัตถุ HttpClient มีวัตถุประสงค์เพื่อให้ทำงานได้ตราบเท่าที่แอปพลิเคชันของคุณต้องการส่งคำขอ HTTP การมีวัตถุอยู่ในหลายคำขอทำให้สถานที่สำหรับการตั้งค่า DefaultRequestHeaders และป้องกันไม่ให้คุณระบุสิ่งต่าง ๆ เช่น CredentialCache และ CookieContainer อีกครั้งในทุกคำขอตามที่จำเป็นด้วย HttpWebRequest

หรือแม้กระทั่งเปิด DotPeek


64
เพื่อชี้แจงคำตอบของคุณมันจะถูกต้องหรือไม่ที่จะบอกว่า "คุณไม่จำเป็นต้องกำจัด HttpClient หากคุณถืออยู่ในตำแหน่งที่จะนำมันกลับมาใช้ภายหลัง" ตัวอย่างเช่นหากมีการเรียกใช้เมธอดซ้ำ ๆ และสร้างอินสแตนซ์ HttpClient ใหม่ (แม้ว่าจะไม่ใช่รูปแบบที่แนะนำในกรณีส่วนใหญ่) มันจะถูกต้องหรือไม่ที่จะบอกว่าวิธีการนี้ไม่ควรกำจัดอินสแตนซ์นั้น มันอาจนำไปสู่อินสแตนซ์ที่ยังไม่ถูกเปิดเผยนับพัน กล่าวอีกนัยหนึ่งคุณควรลองและนำอินสแตนซ์นั้นมาใช้ใหม่ แต่ถ้าคุณไม่นำมาใช้ซ้ำคุณควรกำจัด (เพื่อปล่อยการเชื่อมต่อ)
Fernando Correia

8
ฉันคิดว่าคำตอบที่น่าผิดหวัง แต่เข้าใจถูกต้องมันขึ้นอยู่กับ ถ้าฉันต้องถูกตรึงเพื่อให้คำแนะนำทั่วไปที่ทำงานในกรณีส่วนใหญ่ (ฉันไม่เคยพูดทั้งหมด) ฉันขอแนะนำให้คุณใช้คอนเทนเนอร์ IoC และลงทะเบียนอินสแตนซ์ของ HttpClient เป็นซิงเกิล อายุการใช้งานของอินสแตนซ์จะถูกกำหนดขอบเขตให้กับอายุการใช้งานของคอนเทนเนอร์ สิ่งนี้อาจถูกกำหนดขอบเขตในระดับแอปพลิเคชันหรืออาจจะเป็นการร้องขอในเว็บแอปพลิเคชัน
David Peden

25
@FernandoCorreia ใช่ หากด้วยเหตุผลบางอย่างที่คุณสร้างและทำลาย HttpClient ซ้ำ ๆ ดังนั้นคุณควรทิ้ง ฉันไม่แนะนำให้เพิกเฉยต่ออินเทอร์เฟซ IDisposable เพียงแค่พยายามกระตุ้นให้ผู้คนใช้อินสแตนซ์กลับมาใช้ซ้ำ
Darrel Miller

20
เพียงเพื่อเพิ่มความน่าเชื่อถือให้กับคำตอบนี้ฉันได้พูดคุยกับทีม HttpClient วันนี้และพวกเขายืนยันว่า HttpClient ไม่ได้ถูกออกแบบมาให้ใช้ตามคำขอ ควรใช้อินสแตนซ์ของ HttpClient ในขณะที่แอปพลิเคชันไคลเอนต์ยังคงโต้ตอบกับโฮสต์เฉพาะ
Darrel Miller

19
@DavidPeden การลงทะเบียน HttpClient เป็น singleton ฟังดูอันตรายสำหรับฉันเพราะมันไม่แน่นอน ตัวอย่างเช่นทุกคนจะไม่มอบหมายให้Timeoutคุณสมบัติที่จะกระทืบซึ่งกันและกันหรือไม่
Jon-Eric

47

คำตอบปัจจุบันค่อนข้างสับสนและทำให้เข้าใจผิดและพวกเขาขาดความหมาย DNS สำคัญบางอย่าง ฉันจะพยายามสรุปสิ่งที่ชัดเจน

  1. โดยทั่วไปแล้วการพูดIDisposableวัตถุส่วนใหญ่ควรจะถูกกำจัดเมื่อคุณทำเสร็จแล้วโดยเฉพาะอย่างยิ่งพวกที่เป็นเจ้าของทรัพยากรระบบปฏิบัติการ Named / sharedใช้ร่วมกันทรัพยากร HttpClientจะไม่มีข้อยกเว้นเนื่องจากDarrel Millerชี้ให้เห็นว่ามันจัดสรรโทเค็นการยกเลิกและร่างกายร้องขอ / ตอบสนองสามารถเป็นสตรีมที่ไม่มีการจัดการ
  2. อย่างไรก็ตามแนวปฏิบัติที่ดีที่สุดสำหรับ HttpClientกล่าวว่าคุณควรสร้างหนึ่งอินสแตนซ์และนำมาใช้ซ้ำให้มากที่สุด (ใช้สมาชิกที่ปลอดภัยต่อเธรดต่อเธรดในสถานการณ์แบบมัลติเธรด) ดังนั้นในสถานการณ์ส่วนใหญ่คุณจะไม่ทิ้งเพียงเพราะคุณจะต้องได้ตลอดเวลา
  3. ปัญหาเกี่ยวกับเรื่องการใช้ HttpClient เดียวกัน "ตลอดไป" คือว่าการเชื่อมต่อ HTTP พื้นฐานอาจยังคงเปิดให้กับเดิม DNS แก้ไข IP โดยไม่คำนึงถึงการเปลี่ยนแปลง นี้อาจจะเป็นปัญหาในสถานการณ์เช่นสีฟ้าใช้งาน / สีเขียวและ failover มีวิธีการต่างๆในการจัดการกับปัญหานี้วิธีการที่เชื่อถือได้มากที่สุดเกี่ยวกับเซิร์ฟเวอร์ที่ส่งConnection:closeส่วนหัวหลังจากการเปลี่ยนแปลง DNS เกิดขึ้น ความเป็นไปได้อีกประการหนึ่งคือการรีไซเคิลHttpClientบนฝั่งไคลเอ็นต์ไม่ว่าจะเป็นระยะ ๆ หรือผ่านกลไกบางอย่างที่เรียนรู้เกี่ยวกับการเปลี่ยนแปลง DNS ดูhttps://github.com/dotnet/corefx/issues/11224สำหรับข้อมูลเพิ่มเติม (ฉันขอแนะนำให้อ่านอย่างละเอียดก่อนที่จะสุ่มสี่สุ่มห้าโดยใช้รหัสที่แนะนำในบล็อกโพสต์ที่เชื่อมโยง)

ฉันกำจัดมันตลอดเวลาเนื่องจากฉันไม่สามารถสลับพร็อกซีบนอินสแตนซ์ได้)
ed22

หากคุณต้องการกำจัด HttpClient ไม่ว่าด้วยเหตุผลใดก็ตามคุณควรเก็บ HttpMessageHandler รอบ ๆ ตัวแบบคงที่เนื่องจากการกำจัดสิ่งนั้นเป็นสาเหตุของปัญหาที่เกี่ยวข้องกับการกำจัด HttpClient HttpClient มี constructor overload ที่อนุญาตให้คุณระบุว่า handler ที่ให้มาไม่ควรถูกกำจัดซึ่งในกรณีนี้คุณสามารถใช้ HttpMessageHandler กับ HttpClient อินสแตนซ์อื่นได้
Tom Lint

คุณควรยึดมั่นกับ HttpClient ของคุณ แต่คุณสามารถใช้บางอย่างเช่น System.Net.ServicePointManager.DnsRefreshTimeout = 3000; สิ่งนี้มีประโยชน์เช่นหากคุณใช้อุปกรณ์พกพาซึ่งสามารถสลับไปมาระหว่าง wifi กับ 4G
Johan Franzén

18

ในความเข้าใจของฉันการโทรDispose()เป็นสิ่งจำเป็นเฉพาะเมื่อมันล็อคทรัพยากรที่คุณต้องการในภายหลัง (เช่นการเชื่อมต่อเฉพาะ) ขอแนะนำเสมอให้ใช้แหล่งข้อมูลฟรีที่คุณไม่ได้ใช้อีกต่อไปแม้ว่าคุณจะไม่ต้องการอีกต่อไปเพียงเพราะคุณไม่ควรใช้โดยทั่วไปจะถือไปยังแหล่งข้อมูลที่คุณไม่ได้ใช้ (เล่นสำนวนเจตนา)

ตัวอย่างของ Microsoft นั้นไม่ถูกต้อง ทรัพยากรทั้งหมดที่ใช้จะถูกปล่อยออกเมื่อแอปพลิเคชันออก และในกรณีของตัวอย่างนั้นจะเกิดขึ้นเกือบจะทันทีหลังจากที่HttpClientใช้งานเสร็จ ในกรณีที่ชอบโทรอย่างชัดเจนDispose()ค่อนข้างฟุ่มเฟือย

แต่โดยทั่วไปเมื่อชั้นเรียนดำเนินIDisposableการความเข้าใจก็คือคุณควรจะทำDispose()ในทันทีที่คุณพร้อมและสามารถ ฉันวางตัวนี้เป็นจริงโดยเฉพาะในกรณีเช่นHttpClientนั้นมันไม่ชัดเจนเอกสารว่าทรัพยากรหรือการเชื่อมต่อจะถูกจัดขึ้น / เปิด ในกรณีที่การเชื่อมต่อจะถูกนำมาใช้ซ้ำอีกครั้ง [เร็ว ๆ นี้] คุณจะต้องละทิ้งDipose()มัน - คุณไม่ได้ "พร้อม" ในกรณีนั้น

ดูเพิ่มเติมที่: IDisposable.Dispose วิธีและเวลาที่จะโทรทิ้ง


7
มันเหมือนกับว่ามีคนนำกล้วยมาที่บ้านของคุณกินและยืนด้วยเปลือก พวกเขาควรทำอย่างไรกับเปลือก? ... หากพวกเขากำลังออกไปข้างนอกประตูด้วยปล่อยพวกเขาไป หากพวกเขาเกาะติดอยู่ให้โยนมันลงในถังขยะเพื่อไม่ให้เหม็น
svidgen

เพื่อชี้แจงคำตอบนี้คุณกำลังพูดว่า "ไม่จำเป็นต้องทิ้งถ้าโปรแกรมจะจบลงทันทีหลังจากใช้งาน" และคุณควรกำจัดหากโปรแกรมนั้นคาดว่าจะดำเนินการต่อไปอีกระยะหนึ่งเพื่อทำสิ่งอื่น ๆ ?
Fernando Correia

@FernandoCorreia ใช่ถ้าฉันลืมบางสิ่งฉันคิดว่านั่นเป็นหลักการที่ปลอดภัย คิดบ้างไหมในแต่ละกรณี หากคุณกำลังทำงานกับการเชื่อมต่อตัวอย่างเช่นคุณไม่ต้องการDispose()เชื่อมต่อก่อนกำหนดและต้องเชื่อมต่ออีกสองสามวินาทีในภายหลังหากการเชื่อมต่อที่มีอยู่นั้นสามารถใช้ซ้ำได้ ในทำนองเดียวกันคุณไม่ต้องการDispose()รูปภาพหรือโครงสร้างอื่น ๆ ที่ไม่จำเป็นซึ่งคุณอาจต้องสร้างใหม่ในอีกหนึ่งหรือสองนาที
svidgen

ฉันเข้าใจ. แต่ในกรณีเฉพาะของ HttpClient และ HttpClientHandler ที่คำถามนี้เกี่ยวกับพวกเขาจะถือทรัพยากรเปิดเช่นการเชื่อมต่อ HTTP? หากนั่นคือสิ่งที่เกิดขึ้นฉันอาจต้องคิดทบทวนรูปแบบการใช้ใหม่
Fernando Correia

1
@DPeden คำตอบของคุณไม่ได้ขัดแย้งกับฉันเลย หมายเหตุผมบอกว่าคุณควรทิ้ง () กรณีของตนโดยเร็วที่สุดเท่าที่คุณอย่างเต็มที่พร้อมและสามารถ หากคุณวางแผนที่จะใช้อินสแตนซ์อีกครั้งคุณไม่พร้อม
svidgen

9

เลิก () เรียกรหัสด้านล่างซึ่งปิดการเชื่อมต่อที่เปิดโดยอินสแตนซ์ HttpClient รหัสถูกสร้างขึ้นโดย decompiling ด้วย dotPeek

HttpClientHandler.cs - ทิ้ง

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

ถ้าคุณไม่เรียกใช้การกำจัด 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();
}

หากคุณยังไม่ได้ตั้งเวลาว่างให้เป็นอนันต์จะปลอดภัยที่จะไม่โทรออกและปล่อยให้ตัวจับเวลาการเชื่อมต่อที่ไม่ได้ใช้งานเริ่มต้นและปิดการเชื่อมต่อสำหรับคุณแม้ว่ามันจะเป็นการดีกว่าถ้าคุณโทรหา คุณรู้ว่าคุณทำกับอินสแตนซ์ HttpClient และเพิ่มทรัพยากรเร็วขึ้น


1
นอกจากนี้คุณยังสามารถดูรหัสบน GitHub github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/…
TamusJRoyce

8

คำตอบสั้น ๆ : ไม่คำสั่งในคำตอบที่ยอมรับในปัจจุบันไม่ถูกต้อง : "ฉันทามติทั่วไปคือคุณไม่จำเป็นต้องกำจัด HttpClient (ไม่ควร)

คำตอบยาว : ทั้งสองข้อความต่อไปนี้เป็นจริงและสามารถทำได้ในเวลาเดียวกัน:

  1. "HttpClient มีจุดประสงค์เพื่อให้สร้างอินสแตนซ์หนึ่งครั้งและนำกลับมาใช้ใหม่ตลอดอายุการใช้งานของแอปพลิเคชัน" อ้างอิงจากเอกสารอย่างเป็นทางการเอกสารที่เป็นทางการ
  2. IDisposableวัตถุควร / แนะนำให้เป็นที่จำหน่าย

และพวกเขาไม่จำเป็นต้องขัดแย้งกัน มันเป็นเพียงวิธีการที่คุณจัดระเบียบรหัสของคุณเพื่อนำมาใช้ซ้ำHttpClientและยังคงกำจัดมันอย่างถูกต้อง

แม้คำตอบอีกต่อไปยกมาจากของฉันคำตอบอื่น :

มันไม่ได้เป็นเรื่องบังเอิญที่จะเห็นคนในบล็อกโพสต์บางคนโทษว่าHttpClientเป็นIDisposableอินเตอร์เฟซที่ทำให้พวกเขามีแนวโน้มที่จะใช้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

@JesonMartajaya กำจัดเมื่อแอปพลิเคชันของคุณไม่จำเป็นต้องใช้อินสแตนซ์ httpClient อีกต่อไป คุณอาจคิดว่าคำแนะนำดังกล่าวฟังดูคลุมเครือ แต่ในความเป็นจริงมันสามารถปรับให้เข้ากับวงจรชีวิตของHttpClient clientตัวแปรของคุณได้อย่างสมบูรณ์แบบซึ่งเป็นสิ่งที่เขียนโปรแกรม -101 ซึ่งคุณน่าจะทำอยู่แล้ว คุณอาจจะยังสามารถใช้งานได้using (...) {...}เช่นกัน ตัวอย่างเช่นดูตัวอย่าง Hello World ภายในคำตอบของฉัน
RayLuo

7

เพราะมันไม่ปรากฏว่าทุกคนที่ได้กล่าวถึงที่นี่เลยวิธีที่ดีที่สุดใหม่ในการจัดการ HttpClient และ HttpClientHandler ในสุทธิ 2.1 หลักคือการใช้HttpClientFactory

มันแก้ปัญหาส่วนใหญ่ดังกล่าวและ gotchas ในวิธีที่สะอาดและใช้งานง่าย จากโพสต์บล็อกที่ยอดเยี่ยมของ Steve Gordon :

เพิ่มแพ็คเกจต่อไปนี้ในโครงการ. 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 โดยถือว่าปลอดภัย (คุณไม่มีสาย async ที่ค้าง) เพื่อทำเช่นนั้น


ขอบคุณสำหรับความคิดเห็น. นี่เป็นปัญหาที่ค่อนข้างซับซ้อน ฉันขอแนะนำให้อ่านบทความที่ลิงก์ในคำตอบของ DPeden กล่าวโดยสรุปอินสแตนซ์ HttpClient ควรถูกนำมาใช้ใหม่ตลอดอายุการใช้งานของแอปพลิเคชัน หากคุณสร้างอินสแตนซ์ใหม่ซ้ำ ๆ คุณอาจต้องกำจัดอินสแตนซ์เหล่านั้น
Fernando Correia

6
"อินสแตนซ์ HttpClient ควรถูกนำกลับมาใช้ใหม่ตลอดอายุการใช้งานของแอปพลิเคชัน" นี่เป็นความคิดที่ดีสำหรับแอปพลิเคชันจำนวนมาก ฉันคิดว่าเว็บแอปพลิเคชันที่ใช้ HttpClient HttpClient จะเก็บสถานะไว้ (ตัวอย่างเช่นส่วนหัวคำขอที่จะใช้) ดังนั้นหนึ่งเธรดคำขอของเว็บจึงสามารถเหยียบย่ำสิ่งที่กำลังทำอยู่ได้อย่างง่ายดาย ในเว็บแอปพลิเคชันขนาดใหญ่ฉันเห็น HttpClient ว่าเป็นปัญหาของปัญหาการเชื่อมต่อที่สำคัญ เมื่อมีข้อสงสัยฉันพูดว่าทิ้ง
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 คุณควรล้างข้อมูล ซึ่งสามารถทำได้โดยการโทร Dispose บน HttpResponseMessage หรือโดยการโทร Dispose / Close บน Stream ที่ได้จาก HttpResonseMessage Content หรือโดยการอ่านเนื้อหาทั้งหมด

ไม่ว่าคุณจะโทรหา Dispose บน HttpClient หรือไม่นั้นขึ้นอยู่กับว่าคุณต้องการยกเลิกคำขอที่รอดำเนินการหรือไม่


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 (ใหม่ Uri ("URL ฐาน"))

  • HttpClient ในฐานะส่วนต่อประสานไม่สามารถเรียกใช้ Dispose ()
  • การกำจัด () จะถูกเรียกโดย Garbage Collector ล่าช้า หรือเมื่อโปรแกรมทำความสะอาดวัตถุผ่านทาง destructor
  • ใช้ Weak References + ตรรกะการล้างข้อมูลล่าช้าเพื่อให้ยังคงมีการใช้งานตราบใดที่มันถูกนำมาใช้ซ้ำบ่อยครั้ง
  • จะจัดสรร HttpClient ใหม่เฉพาะสำหรับแต่ละ URL ฐานที่ส่งผ่านไป เหตุผลที่อธิบายโดยคำตอบของ Ohad Schneider ด้านล่าง พฤติกรรมไม่ดีเมื่อเปลี่ยน URL พื้นฐาน
  • HttpClientHandle อนุญาตให้เยาะเย้ยในการทดสอบ

สมบูรณ์ ฉันเห็นว่าคุณโทรหาDisposeวิธีการที่คุณลงทะเบียนกับ GC สิ่งนี้ควรได้รับการจัดอันดับสูงกว่าอยู่ด้านบน
Jeson Martajaya

โปรดทราบว่า HttpClient ทำการรวมทรัพยากรต่อ URL พื้นฐาน ดังนั้นหากคุณเข้าชมเว็บไซต์หลายพันรายการในรายการประสิทธิภาพของคุณจะลดลงโดยไม่ต้องทำความสะอาดแต่ละเว็บไซต์ นี่เป็นการเปิดเผยความสามารถในการกำจัดแต่ละ URL ฐาน อย่างไรก็ตามหากคุณใช้เว็บไซต์เพียงแห่งเดียวอาจเป็นเพราะเหตุผลทางวิชาการในการโทรไปกำจัด
TamusJRoyce

1

การใช้การฉีดพึ่งพาคอนสตรัคเตอร์ของคุณทำให้การจัดการอายุการใช้งานของคุณHttpClientง่ายขึ้นโดยใช้การจัดการอายุการใช้งานนอกรหัสที่ต้องการและทำให้เปลี่ยนได้ง่ายในภายหลัง

การตั้งค่าปัจจุบันของฉันคือการสร้างชั้นไคลเอนต์ http แยกที่สืบทอดจากHttpClientหนึ่งครั้งต่อโดเมนปลายทางปลายทางและทำให้มันเป็นซิงเกิลตันโดยใช้การฉีดพึ่งพาpublic class ExampleHttpClient : HttpClient { ... }

จากนั้นฉันใช้ Constructor ในไคลเอนต์ http ที่กำหนดเองในคลาสบริการที่ฉันต้องการเข้าถึง API นั้น วิธีนี้ช่วยแก้ปัญหาอายุการใช้งานและมีข้อดีเมื่อต้องรวมการเชื่อมต่อ

คุณสามารถดูตัวอย่างการทำงานในคำตอบที่เกี่ยวข้องได้ที่https://stackoverflow.com/a/50238944/3140853


0

โปรดอ่านคำตอบของฉันสำหรับคำถามที่คล้ายกันซึ่งโพสต์ไว้ด้านล่าง ควรชัดเจนว่าคุณควรปฏิบัติต่อHttpClientอินสแตนซ์เป็นซิงเกิลตันและนำกลับมาใช้ใหม่ระหว่างการร้องขอ

ค่าใช้จ่ายในการสร้าง HttpClient ใหม่ต่อการโทรในไคลเอนต์ WebAPI คืออะไร


-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.