لا تُحِط HttpClient بـ using ── الممارسة العملية لاتصال HTTP في تطبيقات C# للأعمال (أنماط الإنشاء والمهلة وإعادة المحاولة)
· آخر تحديث: · غو كومورا · CSharp, .NET, HttpClient, IHttpClientFactory, الشبكات, async/await, تطوير Windows, الاستشارات التقنية
سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621668)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
غو كومورا (2026). لا تُحِط HttpClient بـ using ── الممارسة العملية لاتصال HTTP في تطبيقات C# للأعمال (أنماط الإنشاء والمهلة وإعادة المحاولة). شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621668 https://comcomponent.com/ar/blog/csharp-httpclient-practical-guide/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621668
- DOI (هذه النسخة)
- 10.5281/zenodo.22241024
«بعد الظهر تبدأ الاتصالات بواجهات API الخارجية بالفشل بخطأ SocketException» أو «بعد تبديل الخادم الوجهة يستمر التطبيق بالاتصال بالخادم القديم» — استدعاء GetAsync على HttpClient في C# سهل إن اكتفيت بالاستدعاء، لكن الخطأ في كيفية إنشاء النسخة والاحتفاظ بها يزرع هذا النوع من الأعطال التي «تعمل حينها، ثم تنكسر بعد دخول التشغيل الفعلي».
في هذا المقال، وبافتراض أن تطبيق Windows للأعمال يستدعي واجهة Web API خارجية أو خدمة داخلية، نرتّب النمط الصحيح لإنشاء HttpClient، وتصميم المهلة، وإعادة المحاولة، ومعالجة الأخطاء، وحتى المطبّات الخاصة ببيئة Windows، بالترتيب الذي يكثر التردد عنده في العمل.
1. الخلاصة أولاً (جدول القرار)
طريقة الاحتفاظ بـ HttpClient تختلف حسب بنية التطبيق. نبدأ بجدول القرار.
نفتح أولاً المختصرات الظاهرة في الجدول. DI (Dependency Injection: حقن التبعية) أسلوب تصميم لا تصنع فيه الفئة الجزء الذي تحتاجه بـ new بل تتلقاه من الخارج، وفي .NET يكون الوعاء Microsoft.Extensions.DependencyInjection. Generic Host مضيف عام في .NET يتولى فوق حاوية DI تلك الإعداد والسجلات وتشغيل المعالجة المقيمة وإيقافها، وقد تناولناه في ما هو Generic Host واستخدام Generic Host + BackgroundService في تطبيق سطح المكتب. IHttpClientFactory جهة توريد HttpClient فوق ذلك DI (الفصل 4). نشرح بالترتيب من الوسيلة بلا DI (static + PooledConnectionLifetime) حتى يُقرأ المقال حتى في تطبيق لا يستخدم DI.
| شكل التطبيق | النمط الموصى به | السبب |
|---|---|---|
| أداة كونسول تنتهي في ثوانٍ إلى دقائق (.NET) | HttpClient واحد static/مفرد |
في عملية قصيرة العمر يمكن تجاهل مشكلة تغيّر DNS عملياً |
| تطبيق مقيم طويلاً أو خدمة Windows (.NET، بلا DI) | HttpClient من نوع static + ضبط SocketsHttpHandler.PooledConnectionLifetime |
يحل استنزاف المقابس ومشكلة تغيّر DNS معاً |
| تطبيق يستخدم Generic Host / DI (.NET) | IHttpClientFactory (AddHttpClient) |
يمكن ترك تجميع المعالجات واستبدالها للمصنع. يمكن فصل إعداد كل وجهة بعميل مسمّى/مُنمَّط |
| تطبيق .NET Framework | إدخال IHttpClientFactory بحزمة Microsoft.Extensions.Http |
في .NET Framework يسهل استنزاف المنافذ عند الإنشاء الذاتي، والوثائق الرسمية أيضاً توصي بالمصنع1 |
| إعداد البروكسي أو Cookie أو الشهادة يختلف حسب الوجهة | افصل HttpClient حسب الإعداد (لا تُعد الاستخدام) |
إعداد اتصال المعالج لا يمكن تغييره بعد إرسال أول طلب2 |
بعد ذلك نكتب الخلاصة أولاً.
- لا تنشئ
new HttpClient()وتتخلّص منه بـusingعند كل طلب.HttpClientيحمل مجمّع اتصالات داخلياً، ومصمَّم على افتراض إعادة الاستخدام. الإنشاء والتخلّص في كل مرة يستنفد المقابس المتاحة تحت الحمل المرتفع فيظهرSocketException2. - ومع ذلك لا يكفي جعله
static.HttpClientلا يحل DNS إلا عند إنشاء الاتصال، لذلك يستمر في استخدام الاتصال القديم حتى لو تغيّر عنوان IP للوجهة. قطع عمر الاتصال بـSocketsHttpHandler.PooledConnectionLifetimeهو الحل الموصى به رسمياً1. - إن كنت تستخدم Generic Host أو DI فاترك الأمر لـ
IHttpClientFactory. يجمع المصنع المعالجات ويستبدلها كل دقيقتين افتراضياً، فيتعامل مع استنزاف المقابس وتغيّر DNS معاً3. - القيمة الافتراضية للمهلة 100 ثانية. في إحساس تطبيق الأعمال هذا طول لا يختلف كثيراً عن «الجمود بلا نهاية»، فاضبطه صراحة حسب متطلبات كل وجهة.
- لا تصنع إعادة المحاولة بنفسك، وابدأ بالمعالج القياسي في
Microsoft.Extensions.Http.Resilience. يُدخل إعادة المحاولة وقاطع الدائرة والمهلة كمجموعة قيم افتراضية مجرَّبة، ويتجنّب في مرحلة التصميم حوادث شائعة في حلقة إعادة المحاولة المصنوعة يدوياً مثل «إعادة إرسال POST فاشل بلا شرط فيحدث تسجيل مزدوج»4.
أمثلة الشيفرة في هذا المقال كلها شذرات اقتُطعت بما يلزم للشرح. logger هو Microsoft.Extensions.Logging.ILogger، وct هو CancellationToken الممرَّر من جهة الاستدعاء، وurl وclient يشيران إلى عنوان الوجهة ونسخة HttpClient، وكلها مفترضة جاهزة في الفئة أو الدالة المحيطة. لن تعمل إن لصقتها كما هي في Main.
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 19، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. لماذا لا يصح «الإنشاء في كل مرة داخل using» ── استنزاف المقابس
لأن HttpClient ينفّذ IDisposable، تبدو الشيفرة التالية صحيحة للوهلة الأولى.
// アンチパターン: リクエストのたびに生成して破棄する
public async Task<string> GetDataAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
المشكلة أن المقبس على مستوى نظام التشغيل لا يُحرَّر فوراً حتى بعد Dispose. حسب مواصفات TCP يبقى مقبس الجهة التي أغلقت في حالة TIME_WAIT بعض الوقت. ما دام تكرار الاستدعاء منخفضاً لا يحدث شيء، لكن إن ارتفع الحمل تتراكم المقابس غير المحرَّرة، ثم فجأة يتعذّر الاتصال بـ SocketException2.
غير أن «بقاء المقبس» وحده لا يُقنع بسبب الوصول إلى الاستنزاف. ننظر درجة أعمق في الداخل. المفتاح أن رقم المنفذ في جهتك، الذي يُستهلك عند كل اتصال، محدود.
- عندما يفتح العميل اتصال TCP إلى الخادم، منفذ الوجهة محدد (443 في HTTPS)، لكن رقم المنفذ في جهتك تخصّصه نظام التشغيل تلقائياً من الشواغر. هذا هو المنفذ العابر (ephemeral port، وفي مصطلح Windows المنفذ الديناميكي).
- النطاق الافتراضي للمنافذ الديناميكية في Windows هو 49152 إلى 65535، أي 16,384 منفذاً5. هذا المجمّع الذي تخصّص منه نظام التشغيل عند فتح اتصال صادر، ويُضبط لكل نقل (TCP/UDP) ولكل IPv4/IPv65. ليس سقفاً «حتى 16,384 اتصالاً متزامناً لكل جهاز». يميّز TCP الاتصال بالرباعية «عنوانك ومنفذك» و«عنوان الطرف ومنفذه»6، لذلك يجوز مواصفاتً أن يحمل الرقم نفسه اتصالات متعددة لوجهات مختلفة. ما يعمل ليس المجموع نفسه بقدر بأي سرعة ينحت عملية واحدة المجمّع.
- حتى بعد إغلاق الاتصال، لا يمكن إعادة استخدام منفذ الجهة التي أغلقت أولاً فوراً. منعاً لتسلل رزمة قديمة متأخرة إلى اتصال جديد فُتح بالتركيبة نفسها من المنافذ، يُبقي TCP حالة TIME-WAIT زمناً محدداً (2×MSL: ضعف Maximum Segment Lifetime)6.
- النتيجة أن تصميم «افتح اتصالاً وأغلقه عند كل طلب» يعني حجز منفذ واحد لكل طلب، عشرات الثواني إلى بضع دقائق، كمستخدَم. بمئة طلب في الثانية تنحت إطار الـ 16,384 بسرعة 100 في الثانية، فيُستنفد في دقائق.
من يحمل مجمّع الاتصالات ليس HttpClient نفسه بل المعالج داخله. إعادة إنشاء النسخة تعيد إنشاء المجمّع كله، وهذا مدخل الاستنزاف.
flowchart TB
B1["new ثم Dispose بـ using عند كل طلب"] --> B2["يُعاد إنشاء المعالج كله<br/>مجمّع الاتصالات فارغ في كل مرة"]
B2 --> B3["يُفتح اتصال TCP في كل مرة<br/>وبعد الإغلاق يبقى المنفذ في TIME-WAIT"] --> B4["النحت المستمر للمنافذ = استنزاف المقابس"]
G1["إعادة استخدام HttpClient واحد static"] --> G2["المعالج يحتفظ بمجمّع الاتصالات باستمرار"]
G1b["استلام العميل من المصنع في كل مرة<br/>(المُعاد استخدامه المعالج لا العميل - الفصل 4)"] --> G2
G2 --> G3["إعادة استخدام اتصال قائم"] --> G4["لا يُستهلك منفذ جديد"]
الشكل 1: ما يُعاد استخدامه هو مجمّع الاتصالات الذي يحمله المعالج. إعادة إنشاء HttpClient ترمي المجمّع معه.
إن استُنفد المجمّع يفشل طلب الاتصال التالي لانعدام منفذ يُخصَّص. العرض المحير ظاهرياً الاتصال وحده يتعذّر رغم وفرة المعالج والذاكرة سببه أن المورد المستنزَف مورد آخر هو رقم المنفذ. غير أن ظهور SocketException ≠ استنزاف. توقف الطرف، وقطع المسار، وDNS، وفشل TLS تصعد بالنوع نفسه من الاستثناء. العزل بالإجراء في الفقرة التالية. تنص إرشادات Microsoft الرسمية أيضاً على أن منفذ TCP لا يُحرَّر فوراً حتى بعد إغلاق الاتصال، وأن تكرار الطلبات العالي قد يبلغ سقف عدد المنافذ المتاحة في نظام التشغيل1.
ما يجعل هذا العطل عنيداً أنه نادراً ما يُعاد إنتاجه أثناء التطوير والاختبار. بضع عمليات يدوية في الثانية لا تنحت الإطار حتى القاع، فيظهر بشكل سقوط في ساعة الذروة في الإنتاج فقط، أو سقوط عند دفعة نهاية الشهر فقط. عند التحقيق، تحقق بعدد مقابس TIME_WAIT ونطاق المنافذ في بيئتك بالأمر التالي عند حدوث الظاهرة.
# TIME_WAIT状態のソケット数を数える
(netstat -ano | Select-String "TIME_WAIT").Count
# 自分の環境の動的ポート範囲を確認する(既定は開始49152・16384個)
netsh int ipv4 show dynamicport tcp
هنا لا تقارن مجموع TIME_WAIT للجهاز كله بنطاق 16,384. إن كان الجهاز يتصل بوجهات كثيرة قد يكبر المجموع، لكنك حينها تعد اتصالات سليمة لوجهات أخرى، ولا يعني ذلك أن تطبيقك قريب من الاستنزاف. تنص إجراءات العزل لدى Microsoft أيضاً على أن كثرة TIME_WAIT نفسها ليست دليلاً على الاستنزاف (تدل فقط على احتمال الاستنزاف مستقبلاً)7.
ما ينبغي النظر إليه هو التفصيل حسب العملية وحسب الوجهة وطريقة الازدياد.
# プロセスと状態で束ねる。1つのPIDにTIME_WAITが偏っていれば、そのプロセスが犯人
Get-NetTCPConnection | Group-Object State, OwningProcess |
Sort-Object Count -Descending | Select-Object -First 10 Count, Name
# 疑わしい宛先だけを、ローカルアドレスと相手側エンドポイントで束ねる
Get-NetTCPConnection -State TimeWait |
Group-Object LocalAddress, RemoteAddress, RemotePort |
Sort-Object Count -Descending | Select-Object -First 10 Count, Name
بعد ذلك تحقق بالأعراض مما إذا كان الاستنزاف حقيقياً. إجراءات التحقق التي تذكرها Microsoft ثلاث نقاط7.
| ما يُتحقق منه | طريقة النظر |
|---|---|
| هل تفشل الاتصالات الصادرة جملة | جرّب الوصول إلى مشاركة، وRDP إلى خادم آخر، وtelnet. إن فشل وجهة واحدة فقط فليس استنزافاً |
| سجل الأحداث | معرّف الحدث 4227 / 4231 في سجل النظام |
| الانحياز إلى PID محدد | هل يتركز TIME_WAIT على PID واحد في netstat -anob |
«طريقة الازدياد» أدلة أوثق من العدد المطلق. إن ازداد بخط صاعد فور إعادة تشغيل التطبيق ولم ينخفض دقائق حتى بعد إيقاف الحمل، يمكن شبه الجزم أن نمط الإنشاء هو السبب. وبالعكس، إن زاد ونقص مع الحمل ثم استقر فالاتصالات تُعاد استخدامها. كذلك قد تنفصل مقابس TIME_WAIT عن العملية فلا يمكن تحديد تطبيق الاستدعاء من عمود PID. في تلك الحالة ضيّق بعمود العنوان الخارجي (الوجهة). في Windows 10 / Windows Server 2016 وما بعد، يمكن أيضاً رؤية المنافذ التي خرجت من TIME_WAIT وصارت BOUND عبر netstat -anobq أو Get-NetTCPConnection7.
لاحظ أن HttpClient المستلم عبر IHttpClientFactory خارج هذا الحديث. عميل المصنع إن نُفّذ عليه Dispose لا يُتخلَّص من المعالج (كيان مجمّع الاتصالات)، لذلك إحاطته بـ using آمنة3.
3. جعله static لا ينهي القصة ── مشكلة تغيّر DNS
جعل HttpClient من نوع static اتجاه صحيح كإجراء ضد استنزاف المقابس، لكن مشكلة أخرى تبقى. HttpClient لا يحل DNS إلا عند إنشاء الاتصال، ولا يرجع إلى TTL لسجل DNS2. ما دام الاتصال حياً في المجمّع يستمر في الاتصال بالعنوان القديم حتى لو تغيّر عنوان IP للوجهة.
عطل «بدّلنا DNS عند التحوّل عند الفشل، لكن التطبيق ظل يرى الخادم القديم حتى إعادة التشغيل» سببه هذه الآلية. الحل الذي توصي به الإرشادات الرسمية قطع عمر الاتصال بـ SocketsHttpHandler.PooledConnectionLifetime1.
// .NET (Core) / .NET 5+ での推奨パターン:
// 接続を一定周期で作り直させることで、DNS変更に追従する
private static readonly HttpClient SharedClient = new(new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
});
الاتصال الذي بلغ عمره يُعاد إنشاؤه عند الطلب التالي، وفي ذلك التوقيت يُحل DNS من جديد. تُحدَّد القيمة بحسب مدى السرعة التي تريد بها متابعة تغيّر DNS. مثال الوثائق الرسمية يستخدم دقيقتين، لكن نظاماً داخلياً نادراً ما تتغيّر وجهته يمكن إطالة القيمة بلا مشكلة1.
لاحظ أن SocketsHttpHandler تنفيذ من .NET Core 2.1 فما بعد، و.NET Framework لا يدعمه. في حالة .NET Framework استخدم IHttpClientFactory في الفصل التالي1.
نرتّب هنا البيئة المستهدفة في موضع واحد. من الوسائل الظاهرة في هذا المقال، تحقق بهذا الجدول أيها يعمل في بيئتك.
| الوسيلة | .NET Framework 4.8 | .NET Core 2.1+ / .NET 5+ |
|---|---|---|
HttpClient نفسه |
يعمل (التنفيذ الداخلي HttpWebRequest / ServicePoint) |
يعمل (التنفيذ الداخلي SocketsHttpHandler) |
SocketsHttpHandler.PooledConnectionLifetime |
لا يعمل | يعمل. الخيار الأول لمتابعة DNS1 |
IHttpClientFactory |
يعمل بحزمة Microsoft.Extensions.Http. توصية رسمية1 |
يعمل |
ServicePointManager.DefaultConnectionLimit |
يعمل (انتبه لصغر القيمة الافتراضية 2)8 | لا يؤثّر على HttpClient. يُحدَّد السقف بـ MaxConnectionsPerServer |
ServicePoint.ConnectionLeaseTimeout |
يعمل (أدناه) | لا يحمل معنى فعلياً9 |
الخيار الأول في .NET Framework، وفق الإرشادات الرسمية، إدخال IHttpClientFactory بحزمة Microsoft.Extensions.Http1. غير أن كثيراً من المواقع تقول «تطبيق WinForms كبير قائم، ولا متسع للبدء بإدخال DI أولاً». الحل الوسط حينها مشاركة HttpClient واحد، وقطع عمر الاتصال بـ ServicePoint.ConnectionLeaseTimeout.
using System;
using System.Net;
using System.Net.Http;
// .NET Framework 4.8 での折衷案:
// HttpClientは共有し、接続の寿命はServicePoint側で区切ってDNS変更に追従させる
private static readonly Uri ApiBase = new Uri("https://order.example.co.jp/");
private static readonly HttpClient SharedClient = CreateSharedClient();
private static HttpClient CreateSharedClient()
{
// 最初のリクエストより前に必ず通す。ここを呼び忘れると
// ConnectionLeaseTimeout は既定の -1(接続を無期限に維持)のままで、
// DNS変更に追従しない=この節の対策が何も効いていない状態になる
ServicePoint sp = ServicePointManager.FindServicePoint(ApiBase);
sp.ConnectionLeaseTimeout = (int)TimeSpan.FromMinutes(2).TotalMilliseconds; // 単位はミリ秒
return new HttpClient { BaseAddress = ApiBase };
}
ConnectionLeaseTimeout إعداد «بعد انقضاء الزمن المحدد، أغلق ذلك الاتصال عند انتهاء معالجة طلب واحد»، وهو معدّ لحالات تريد فيها إعادة فتح الاتصال دورياً مثل تبديل موازن الحمل9. الاتصال المغلَق يُعاد إنشاؤه في الطلب التالي، وهناك يُحل DNS من جديد. الفكرة مطابقة تماماً لـ PooledConnectionLifetime، فاعتبره المقابل في جانب .NET Framework.
غير أن واجهات WebRequest بما فيها ServicePoint منصوص رسمياً على عدم استخدامها في التطوير الجديد9. هذا علاج لإطالة العمر مع البقاء على .NET Framework، وعند الانتقال إلى .NET استبدله بـ PooledConnectionLifetime أو IHttpClientFactory. منظور الانتقال ملخّص في قائمة التحقق قبل الانتقال من .NET Framework إلى .NET.
فشلا الفصلين حتى هنا يأتيان كلاهما من عدم تقرير «متى يُعاد إنشاء الاتصال». علاقة الفصل 2 والفصل 3 في ورقة واحدة كما يلي.
flowchart TB
Q["كيف تحتفظ بـ HttpClient وبمجمّع الاتصالات"]
Q -->|"new ثم Dispose بـ using عند كل طلب"| A["يُرمى مجمّع الاتصالات في كل مرة<br/>→ نحت مستمر للمنافذ العابرة<br/>= استنزاف المقابس (الفصل 2)"]
Q -->|"الاحتفاظ بواحد static فقط"| B["يبقى الاتصال في المجمّع<br/>→ DNS لا يُحل إلا عند إنشاء الاتصال<br/>= الاتصال بالعنوان القديم بعد التبديل (الفصل 3)"]
Q -->|"مشاركة مجمّع الاتصالات مع منح الاتصال عمراً"| C["إعادة إنشاء الاتصال دورياً<br/>= التعامل مع الاستنزاف وتغيّر DNS معاً"]
C --> C1["الوسيلة حسب البيئة كما في جدول قرار الفصل 1<br/>· .NET 5+ : static + PooledConnectionLifetime<br/>· DI / Generic Host : IHttpClientFactory<br/> (يُستلم العميل في كل مرة، والمشترَك المعالج - الفصل 4)<br/>· .NET Framework : IHttpClientFactory،<br/> وإن تعسّر فـ ConnectionLeaseTimeout"]
الشكل 2: «عدم إعادة الاستخدام» و«إعادة الاستخدام بلا حد» كلاهما فشل. الصواب إعادة استخدام مجمّع الاتصالات مع منح الاتصال عمراً. ما يُشارَك يختلف حسب الوسيلة (في المصنع المعالج لا العميل).
4. عند استخدام DI، اعتمد على IHttpClientFactory
في تطبيق يستخدم Generic Host أو حاوية DI، الخيار الأول IHttpClientFactory (AddHttpClient). شرح Generic Host نفسه في ما هو Generic Host، وإدخاله في تطبيق سطح المكتب في استخدام Generic Host + BackgroundService في تطبيق سطح المكتب.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// 名前付きクライアント: 接続先ごとに設定を分離する
builder.Services.AddHttpClient("OrderApi", client =>
{
client.BaseAddress = new Uri("https://order.example.co.jp/");
client.Timeout = TimeSpan.FromSeconds(10);
});
ما يفعله المصنع أسرع بالشكل. أن HttpClient والمعالج الحامل لمجمّع الاتصالات شيئان مختلفان هو كل هذه الآلية.
flowchart TB
C1["CreateClient المرة 1<br/>(عميل يُستخدم مرة)"]
C2["CreateClient المرة 2<br/>(عميل يُستخدم مرة)"]
C3["CreateClient بعد الاستبدال<br/>(عميل يُستخدم مرة)"]
H1["المعالج 1<br/>كيان مجمّع الاتصالات هنا"]
P1["مجمّع الاتصالات<br/>يعيد استخدام اتصال TCP ولا يرمي المقبس"]
H2["المعالج 2<br/>يُنشأ جديداً"]
P2["مجمّع اتصالات جديد<br/>هنا يُحل DNS من جديد"]
C1 --> H1
C2 --> H1
H1 --> P1
H1 -->|"ينتهي العمر بعد دقيقتين افتراضياً. يتوقف الصرف الجديد،<br/>ويُتخلَّص منه بعد انتهاء المعالجة الجارية"| H2
C3 --> H2
H2 --> P2
الشكل 3: مجمّع معالجات IHttpClientFactory والاستبدال الدوري.
من هذا الشكل تُقرأ الخلاصتان المكتوبتان في صدر المقال. «الاستخدام لمرة مرفوض» لأن new HttpClient() الذاتي يجعل الطبقة العليا (العميل) والسفلى (المعالج ومجمّع الاتصالات) تموتان معاً واحداً بواحد، فترمي المقبس أيضاً عند كل رمي. «static أيضاً ناقص» لأن static يثبّت الطبقة السفلى فلا تُتخلَّص منها، فتحمي المقابس لكن سهم استبدال المعالج (= إعادة حل DNS) لا يحدث مرة. المصنع يبقي الطبقة العليا للاستخدام لمرة، ويشارك الطبقة السفلى فقط، ويستبدلها دورياً، فيلبّي الأمرين معاً.
ثلاث نقاط تُثبَّت في حركة المصنع.
- المعالجات تُجمَّع وتُستبدل كل دقيقتين افتراضياً. كل
CreateClientيُرجعHttpClientجديداً، لكن المعالج في الخلف (مجمّع الاتصالات) مشترك فلا يحدث استنزاف مقابس، والاستبدال الدوري يتابع تغيّر DNS أيضاً3. HttpClientالمصنوع عبر المصنع مفترض قصير العمر. إن احتفظت بالنسخة المستلمة في حقل مفرد، خرجت من مشاركة استبدال المعالج وتوقفت عن متابعة تغيّر DNS. حقن عميل مُنمَّط في خدمة مفردة يُتجنَّب للسبب نفسه3.- تنبيه في التطبيقات المعتمدة على Cookie. نتيجة تجميع المعالجات تُشارَك
CookieContainerدون قصد. إن استخدمت Cookie، توجّه الوثائق الرسمية إلى تجنّب المصنع أو تعطيل معالجة Cookie وإرفاق الترويسة بنفسك1.
حول الحصول على الرمز عند استدعاء واجهة API محمية (مثل واجهة محمية بـ Microsoft Entra ID) تناولناه في إدخال مصادقة Entra ID في تطبيق WinForms/WPF، فراجعه هناك.
5. تصميم المهلة ── القيمة الافتراضية 100 ثانية طويلة جداً لتطبيق أعمال
القيمة الافتراضية لـ HttpClient.Timeout هي 100 ثانية10. انتظار 100 ثانية في تطبيق أعمال يستدعي API امتداداً لعملية على الشاشة يعادل «تجمّد»، فاضبط صراحة حسب كل وجهة.
// クライアント全体の既定タイムアウト
client.Timeout = TimeSpan.FromSeconds(10);
// 特定のリクエストだけ短く/長くしたい場合はCancellationTokenSourceを使う
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
HttpResponseMessage response = await client.GetAsync(url, cts.Token);
تنبيهات التصميم كما يلي.
- الاستثناء عند المهلة هو
TaskCanceledException. من .NET 5 فما بعد، مهلة ناشئة عنHttpClient.TimeoutتحملTimeoutExceptionكاستثناء داخلي10. غير أن المهلة عبرCancellationTokenSourceالذاتي كما أعلاه لا تحمل استثناء داخلياً. التمييز بين المهلة والإلغاء من المستخدم أوثق بالحكم على «هل أُلغي الرمز الممرَّر من جهة الاستدعاء» لا بالاستثناء الداخلي (مثال الشيفرة في الفصل 7). إن نظرت في كتلة catch إلىHttpRequestExceptionفقط أفلتت المهلة، فانتبه. Timeoutقيد على «الطلب كله». إن أردت تقييد إنشاء الاتصال وحده بقصر، اجمع معهSocketsHttpHandler.ConnectTimeout. متطلب «إن سقط الخادم أريد الاستسلام في 3 ثوانٍ، لكن الاستجابة الكبيرة السليمة أريد انتظارها 60 ثانية» يُعبَّر عنه بهذا الجمع2.- تجنّب التخزين المؤقت الافتراضي عند تنزيل ملف كبير.
HttpClientيقرأ الاستجابة كلها إلى الذاكرة افتراضياً، لذلك في تنزيل عشرات الميغابايت فما فوق حدّدHttpCompletionOption.ResponseHeadersReadوعالج بالتدفق2.
إخراج قيمة المهلة إلى appsettings.json وتغييرها حسب البيئة يمكن استخدام جدول القرار في الممارسة العملية لإدارة الإعداد في تطبيقات Windows للأعمال كما هو.
لاحظ أن استدعاء اتصال HTTP من WinForms/WPF مع الحظر بـ .Result أو .Wait() يسبب جمود خيط الواجهة. هذا المطبّ الثابت مشروح بالتفصيل في جدول قرار الممارسة لـ async/await في C#، فيُنصح بقراءته قبل كتابة معالجة الاتصال.
6. إعادة المحاولة ── استخدم معالج resilience القياسي بدل حلقة مصنوعة يدوياً
الشبكة تفشل مؤقتاً، لذلك تحتاج معالجة استدعاء واجهة API خارجية إلى إعادة محاولة. غير أن إعادة المحاولة المصنوعة بحلقة for وTask.Delay تتطلب تنفيذ النقاط التالية كلها بنفسك بشكل صحيح، فلا تستحق العناء.
- تمييز الفشل الذي تجوز إعادة محاولته (المهلة، HTTP 408/429/5xx) عن الفشل الذي لا جدوى منه (HTTP 400/401/404)
- استبعاد طرائق HTTP التي تُحدث حادثاً عند إعادة التنفيذ (التنفيذ المزدوج للتسجيل عبر POST مثلاً)
- التراجع الأسي وjitter (تقلقل يمنع كل العملاء من إعادة الإرسال معاً فيسقط الخادم من جديد)
- قاطع دائرة يوقف الاستدعاء نفسه حين يستمر العطل
المعالج القياسي في حزمة Microsoft.Extensions.Http.Resilience يوفّر هذه المجموعة بقيم افتراضية مجرَّبة4.
builder.Services.AddHttpClient("OrderApi", client =>
{
client.BaseAddress = new Uri("https://order.example.co.jp/");
})
.AddStandardResilienceHandler(); // リトライ+サーキットブレーカー+タイムアウトの標準セット
القيم الافتراضية للمعالج القياسي: مهلة الطلب كله 30 ثانية، حتى 3 محاولات بتراجع أسي (تأخير أولي ثانيتان مع jitter)، مهلة 10 ثوانٍ لكل محاولة، ومعاملة HTTP 408/429/5xx وHttpRequestException كخطأ مؤقت4.
نقطة واحدة تستدعي الانتباه في الافتراضي: المعالج القياسي يعيد المحاولة افتراضياً لكل طرائق HTTP. في واجهة API يضرّها التنفيذ المزدوج لـ POST من نوع التسجيل، عطّل إعادة المحاولة للطرائق غير الآمنة4.
httpClientBuilder.AddStandardResilienceHandler(options =>
{
// POST/PUT/DELETE等の再実行を無効化する
options.Retry.DisableForUnsafeHttpMethods();
});
لاحظ أن إعادة المحاولة تحل الفشل المؤقت فقط. أعراض مثل «الاتصال قائم لكن البيانات لا تسري» أو «الاستجابة بطيئة جداً» كثيراً ما تكون مشكلة في طبقة TCP، وعزلها يستفيد من الأسلوب الذي تناولناه في سبب توقف اتصال الكاميرا الصناعية بإعادة إرسال TCP وعزله.
7. معالجة الأخطاء ── كيف تُعامل رموز الحالة
HttpClient لا يرمي استثناء عند فشل «عادت استجابة HTTP» مثل HTTP 404 أو 500. الاستثناء يحدث عندما لا تُحصل الاستجابة نفسها: فشل الاتصال، المهلة، الإلغاء. اكتب النوعين وعيهما منفصلين.
try
{
using HttpResponseMessage response = await client.GetAsync(url, ct);
if (!response.IsSuccessStatusCode)
{
// 応答は返ってきたが失敗: ステータスコードで分岐できる
if (response.StatusCode == HttpStatusCode.NotFound)
{
return null; // 「存在しない」を正常系として扱う例
}
response.EnsureSuccessStatusCode(); // それ以外はHttpRequestExceptionへ
}
return await response.Content.ReadFromJsonAsync<Order>(ct);
}
catch (HttpRequestException ex)
{
// فشل الاتصال، أو حالة فشل من EnsureSuccessStatusCode.
// من .NET 5 فصاعدا يمكن قراءة رمز الحالة عند الإخفاق عبر ex.StatusCode
logger.LogError(ex, "فشل استدعاء API الطلبات. StatusCode={StatusCode}", ex.StatusCode);
throw;
}
catch (TaskCanceledException) when (ct.IsCancellationRequested)
{
// إلغاء عبر الرمز الذي مرّره المستدعي (إغلاق الشاشة مثلا).
// ليس خطأ، لذا انشره كما هو دون تلويث السجل
throw;
}
catch (TaskCanceledException ex)
{
// HttpClient.Timeout، أو انتهاء CTS مهلة ذاتي
logger.LogError(ex, "انتهت مهلة API الطلبات");
throw;
}
حكم مثل «هل تُعامل 404 كاستثناء أم كـ null» يتبع دلالات واجهة API الوجهة. إن جعلت كل شيء استثناء بضربة EnsureSuccessStatusCode واحدة تضخمت كتلة catch عند جهة الاستدعاء. خط الفصل بين ما يكون استثناء وما يُعبَّر عنه بقيمة الإرجاع ينطبق عليه تفكير الممارسة العملية لالتقاط الاستثناء والسجل ومعالجة الأخطاء كما هو.
إرسال JSON واستقباله بـ GetFromJsonAsync / PostAsJsonAsync / ReadFromJsonAsync من System.Net.Http.Json يغنيك عن كتابة التسلسل عبر سلسلة بنفسك.
8. مطبّات خاصة بتطبيقات Windows للأعمال
أخيراً نلخص مطبّات تُداس كثيراً في عمل بيئة Windows.
- أول طلب بطيء بسبب الكشف التلقائي عن البروكسي. افتراض Windows أن
HttpClientيستخدم إعداد بروكسي نظام التشغيل (بما فيه الكشف التلقائي). إن عرفت أن البروكسي غير مطلوب، عطّل بـHttpClientHandler.UseProxy = falseفيزول انتظار الكشف2. وبالعكس، في بيئة يلزم فيها بروكسي داخلي، التحديد الصريح بـWebProxyيتجنّب «يعمل على جهاز التطوير ولا يعمل على الخادم». - أنهِ إعداد البروكسي قبل أول طلب. إعدادات اتصال المعالج لا تسري إن غيّرتها بعد إرسال طلب مرة2.
- القيمة الافتراضية لعدد الاتصالات المتزامنة عكس في .NET و.NET Framework. في .NET (
SocketsHttpHandler) عدد الاتصالات المتزامنة لـ HTTP/1.1 غير محدود افتراضياً، فقد تزداد الاتصالات بطلبات متوازية كثيرة وتصطدم بحد الجدار الناري أو الخادم. في معالجة ذات توازٍ عالٍ ضع سقفاً بـMaxConnectionsPerServer2. وبالعكس في .NET Framework القيمة الافتراضية لـServicePointManager.DefaultConnectionLimitهي 2 (في بيئة غير ASP.NET)، فتنتظر الطلبات المتوازية داخلياً وتحدث مهلة. عند رفع التوازي في .NET Framework ارفع هذا السقف صراحة8. - عند الاستدعاء من خدمة Windows يختلف سياق البروكسي وTLS عن المستخدم. حساب تشغيل الخدمة لا يملك إعداد بروكسي المستخدم ولا بيانات اعتماده، فيصبح هذا سبباً ثابتاً لعطل اتصال «يعمل مع المستخدم التفاعلي ولا يعمل من الخدمة». سياق التشغيل الخاص بالخدمة في كيفية صنع خدمة Windows وتشغيلها.
- لا تُضمّن عنوان URL للوجهة أو مفتاح API في الشيفرة. تبديل الوجهة عبر ملف الإعداد (الممارسة العملية لإدارة الإعداد)، وحفظ المعلومات السرية بأسلوب تجنّب الإعداد بالنص الواضح عبر DPAPI.
الخلاصة
جودة ممارسة HttpClient لا تُحسم بـ«كيف تستدعي» بقدر ما تُحسم بـ«كيف تحتفظ». الإنشاء عند كل طلب يجلب استنزاف المقابس، وجعله static بسذاجة يجلب عدم متابعة تغيّر DNS، وكلاهما لا يُرى أثناء التطوير. في .NET الجواب نسخة مشتركة مع PooledConnectionLifetime أو IHttpClientFactory، وفي .NET Framework إدخال IHttpClientFactory. فوق ذلك، اضبط المهلة صراحة حسب الوجهة، واترك إعادة المحاولة للمعالج القياسي لـ resilience — إلى هنا فقط يصبح تطبيق الأعمال قائماً على فرض «شبكة تفشل أحياناً».
مراجعة جانب الاتصال في تطبيق قائم (اتصال يسقط في ساعة الذروة فقط، ترتيب تصميم المهلة، تنفيذ جديد للتكامل مع واجهة API خارجية) كثيراً ما تحتاج حكماً مع النظر في الشيفرة الفعلية وبيئة التشغيل، فإن ترددت فاستشرنا.
مقالات ذات صلة
- جدول قرار الممارسة لـ async/await في C#
- ما هو Generic Host
- استخدام Generic Host + BackgroundService في تطبيق سطح المكتب
- ليس appsettings.json وحده ── الممارسة العملية لإدارة الإعداد في تطبيقات Windows للأعمال
- إدخال مصادقة Entra ID في تطبيق WinForms/WPF
- سبب توقف اتصال الكاميرا الصناعية بإعادة إرسال TCP وعزله
- وهم إمكانية Receive حسب وحدة Send على TCP
- الممارسة العملية لالتقاط الاستثناء والسجل ومعالجة الأخطاء
- حفظ المعلومات السرية في تطبيقات Windows - تجنّب الإعداد بالنص الواضح عبر DPAPI
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع تطوير تطبيقات Windows للأعمال بما فيه التكامل مع واجهات API الخارجية، ومع الاستشارة التقنية لتحقيق أعطال الاتصال في التطبيقات القائمة (استنزاف المقابس، المهلة، فشل الاتصال المتقطع) وترتيب الإصلاح.
المراجع
-
Microsoft Learn، Guidelines for using HttpClient. حول استخدام عميل طويل العمر مع
PooledConnectionLifetimeأو عميل قصير العمر منIHttpClientFactoryفي .NET Core/.NET 5+، والتوصية باستخدامIHttpClientFactoryفي .NET Framework، وتجنّبIHttpClientFactoryفي التطبيقات التي تستخدم Cookie بسبب مشاركةCookieContainer. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 -
Microsoft Learn، HttpClient Class. حول أن الإنشاء عند كل طلب يجلب استنزاف المقابس و
SocketException، وأنه لا يحل DNS إلا عند إنشاء الاتصال ولا يرجع إلى TTL، وأن إعداد اتصال المعالج لا يمكن تغييره بعد أول طلب، وأن عدد الاتصالات المتزامنة لـ HTTP/1.1 غير محدود افتراضياً، والتوصية بالتدفق عند التنزيل الكبير، والسلوك الافتراضي للبروكسي وتعطيله بـUseProxy. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 -
Microsoft Learn، IHttpClientFactory with .NET. حول أن العمر الافتراضي للمعالج دقيقتان، وأن
HttpClientالمصنوع عبر المصنع مفترض قصير العمر، وأنDisposeلعميل المصنع لا يتخلّص من المعالج، وأن حقن عميل مُنمَّط في مفرد يوقف متابعة تغيّر DNS. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn، Build resilient HTTP apps: Key development patterns. حول الاستراتيجية ذات المراحل الخمس التي يكوّنها
AddStandardResilienceHandler(محدد المعدل / مهلة كلية 30 ثانية / حتى 3 محاولات بتراجع أسي / قاطع دائرة / مهلة محاولة 10 ثوانٍ)، ورموز الحالة المستهدفة (408/429/5xx) والاستثناءات، وتعطيل إعادة محاولة POST ونحوه بـDisableForUnsafeHttpMethods. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn، The default dynamic port range for TCP/IP has changed in Windows Vista and in Windows Server 2008. حول أن النطاق الافتراضي للمنافذ الديناميكية من Windows Vista / Windows Server 2008 فما بعد يبدأ من 49152 وينتهي عند 65535، وإمكانية التحقق من النطاق الحالي بـ
netsh int ipv4 show dynamicport tcp. ↩ ↩2 -
IETF، RFC 9293 - Transmission Control Protocol (TCP), Section 3.3.2. حول حالة TIME-WAIT في TCP، وآلية منع تسلل الرزم المتأخرة إلى اتصال لاحق بانتظار 2×MSL (Maximum Segment Lifetime). ومعها أن اتصال TCP يُميَّز بزوج مقابس الطرفين (تركيبة العنوان والمنفذ). ↩ ↩2
-
Microsoft Learn، TCP/IP port exhaustion troubleshooting. حول أن نطاق المنافذ الديناميكية يُضبط لكل نقل، وأن كثرة TIME_WAIT نفسها ليست دليلاً على الاستنزاف بل تدل فقط على احتمال الاستنزاف مستقبلاً، والتحقق بفشل الاتصالات الصادرة جملة ومعرّف الحدث 4227/4231 وانحياز TIME_WAIT إلى PID واحد، وإمكانية رؤية منافذ حالة BOUND أيضاً عبر
netstat -anobqأوGet-NetTCPConnectionفي Windows 10 / Windows Server 2016 فما بعد. ↩ ↩2 ↩3 -
Microsoft Learn، ServicePointManager.DefaultConnectionLimit Property. حول أن عدد الاتصالات المتزامنة الافتراضي 10 في تطبيق مستضاف على ASP.NET، و2 فيما عداه (تطبيق سطح المكتب وغيره). ↩ ↩2
-
Microsoft Learn، ServicePoint.ConnectionLeaseTimeout Property. حول أن القيمة الافتراضية -1 (بلا أجل) والوحدة مللي ثانية، وأن الاتصال يُغلق بعد انقضاء الزمن المحدد عند انتهاء معالجة طلب، وأنه إعداد لحالات تريد إعادة فتح الاتصال دورياً مثل موازنة الحمل، وأن واجهات
WebRequest/ServicePointغير موصى بها في التطوير الجديد، وأنه من .NET 9 فما بعد تُربط هذه الخاصية بـPooledConnectionLifetimeلكنها لا تحمل أثراً فعلياً. ↩ ↩2 ↩3 -
Microsoft Learn، HttpClient.Timeout Property. حول أن القيمة الافتراضية 100 ثانية، وأنه عند المهلة من .NET 5 فما بعد يُرمى
TaskCanceledExceptionيحملTimeoutExceptionكاستثناء داخلي. ↩ ↩2
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
مطبّات محرّك الشبكة ومسار UNC ── التعامل العملي مع خادم الملفّات (المجلّد المشترك) في تطبيقات الأعمال
نرتّب الأعطال الشائعة عند الكتابة إلى مجلّد مشترك أو مراقبته من تطبيق أعمال. نشرح سبب عدم ظهور حرف القرص (Z:) من الخدمة، والصلاحيّات المط...
تحديد سبب «البطء» بـ PerfView وdotnet-trace ── مدخل عملي لتحقيق أداء .NET
عندما يصبح تطبيق الأعمال «بطيئاً» أو «يلتصق بالمعالج»، أي أداة تستخدم وماذا تنظر؟ نرتّب توزيع الأدوار بين PerfView وdotnet-trace، وقراءة ...
ليس appsettings.json وحده ── إدارة الإعدادات عمليّاً في تطبيقات Windows للأعمال (الإعدادات حسب البيئة، الأسرار، وجهة الكتابة)
نرتّب إدارة الإعدادات لتطبيقات Windows للأعمال. نشرح تدرّج appsettings.json، والتمييز بين IOptions وIOptionsMonitor، والإعدادات حسب البيئ...
الطباعة وإخراج PDF في تطبيقات أعمال Windows ── التمييز بين System.Drawing.Printing وWPF ومكتبات التقارير
نرتّب بجدول قرار حسب المتطلبات طباعة WinForms عبر PrintDocument، وطباعة FlowDocument/FixedDocument في WPF، وخيارات إخراج PDF. ونشرح التحك...
انتهاء خدمة برامج تشغيل الطابعات في Windows ── كيف تستعد تطبيقات الأعمال لطباعة التقارير والملصقات
تُنهى Microsoft تدريجيّاً خدمة برامج تشغيل الطابعات v3/v4، ومن تمّوز/يوليو 2026 يُفضَّل برنامج تشغيل فئة IPP. نرتّب في جداول قرار ما يختف...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
تصميم وتنفيذ التكامل مع واجهات API الخارجيّة والاتّصال عبر HTTP يدخلان ضمن نطاق الاستشارات العمليّة لتطوير تطبيقات Windows.
الاستشارات التقنية ومراجعة التصميم
مراجعة أعطال الاتّصال في التطبيقات القائمة (استنزاف المقابس، تصميم المهلات الزمنيّة) تندرج ضمن استشارة تقنيّة تتضمّن مراجعة تصميم.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- HttpClient ينفّذ IDisposable، فلماذا لا يجوز إحاطته بـ using؟
- المشكلة في نمط «الإنشاء والتخلّص عند كل طلب». يحتفظ HttpClient داخلياً بمجمّع اتصالات، وهو مصمَّم على افتراض إعادة استخدامه طوال عمر التطبيق. عند الإنشاء والتخلّص في كل مرة تبقى المقابس في حالة TIME_WAIT بعض الوقت حتى بعد التخلّص، فتُستنزف المقابس المتاحة تحت الحمل المرتفع ويظهر SocketException. تخلّص منه مرة واحدة عند إنهاء التطبيق، أو احصل عليه عبر IHttpClientFactory. أما HttpClient المستلم عبر المصنع فإحاطته بـ using آمنة، لأن Dispose لا يتخلّص من المعالج (handler) الفعلي.
- هل ينبغي الانتقال من WebClient أو HttpWebRequest في .NET Framework؟
- يُنصح بتوحيد الشيفرة الجديدة على HttpClient. WebClient وHttpWebRequest واجهتان قديمتان أُبقيتا للتوافق، وتوصي Microsoft نفسها باستخدام HttpClient في أي تطوير جديد. لا حاجة لاستبدال الشيفرة القائمة دفعة واحدة، لكن الانتقال التدريجي نحو HttpClient عند لمس أجزاء الاتصال يسهّل الصيانة من ناحية التحكم بالمهلة ودعم async وسهولة الاختبار.
- كم مرة ينبغي إعادة المحاولة، وبأي فاصل؟
- الأكثر أماناً الانطلاق من القيم الافتراضية للمعالج القياسي في Microsoft.Extensions.Http.Resilience (حد أقصى 3 محاولات، تراجع أسي مع jitter، وتأخير أولي ثانيتان) بدل تحديدها بنفسك. الأهم من العدد تمييز «هل يجوز إعادة محاولة هذا الطلب؟». العمليات مثل POST التي قد تسبّب تسجيلاً مزدوجاً عند إعادة التنفيذ يجب تعطيل إعادة المحاولة عليها افتراضياً، أو التأكد أن الخادم يضمن idempotency (استقبال الطلب نفسه مرتين لا يغيّر النتيجة) قبل تفعيل إعادة المحاولة.
- لماذا يكون أول طلب فقط بطيئاً جداً في بيئة بروكسي داخلية؟
- بالإعداد الافتراضي في Windows يحاول HttpClient الكشف التلقائي عن البروكسي، وقد يستغرق ذلك وقتاً عند أول اتصال. في البيئات التي يُعرف فيها أن البروكسي غير مطلوب (مثل الاتصال الداخلي بين خوادم)، يحسّن الأمر تعطيل الكشف التلقائي بضبط UseProxy على false في HttpClientHandler. وبالعكس، في البيئات الداخلية التي يلزم فيها بروكسي، يكون التحديد الصريح عبر WebProxy بدل الاعتماد على الكشف التلقائي أكثر استقراراً.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.