اشتباه بزرگ در استفاده از HttpClient

اشتباه بزرگ در استفاده از HttpClient

اگر شما هم با زبان سی شارپ برنامه نویسی می کنید، حتما از کلاس HttpClient بارها استفاده کرده اید.
کلاس HttpClient یک کلاس پایه برای ارسال درخواست و دریافت پاسخ از یک منبع شناخته شده توسط URI می باشد، یا به زبان ساده تر برای ارسال درخواست و دریافت پاسخ توسط URI از این کلاس استفاده می کنیم.

من هم مثل همه شما با توجه به مستندات ارائه شده توسط مایکروسافت برای سالها از این کلاس به صورت زیر استفاده می کردم:

using(var client = new HttpClient())
{
    //Codes
}

در یکی از پروژه ها متوجه شدم نرم افزار به صورت غیر معمولی ناپایدار است! با یک تغییر بسیار کوچک بازدهی نرم افزار بسیار بهتر شد و مشکل به صورت کامل حل شد!
عبارت using یکی از زیبایی های سی شارپ است و با استفاده از using دیگر نگران اشغال بیهوده منابع نخواهیم بود و وقتی بلاک using کامل شود متد dispose صدا زده می شود و منابع به صورت خودکار آزاد می شوند (Dispose).
استفاده از using بسیار معمول است و تغریبا در تمام کارها استفاده میکنیم، از اتصالات بانک اطلاعاتی گرفته تا Stream Writers.
در واقع هر شیئی که از منابع به صورت موقتی استفاده می کند و باید بعد از اتمام کار منابع را آزاد کند باید از اینترفیس IDisposable استفاده کند.
طبق مستندات مایکروسافت زمانی که از یک شیئ IDisposable استفاده می کنیم باید درون بلاک using از آن نمونه سازی کنیم.
طبیعتا وقتی از کلاس HttpClient استفاده می کنیم نیاز داریم تا بعد از استفاده کانکشن بسته شود و منابع آزاد شوند، ولی HttpClient کمی متفاوت است، بجای نمونه سازی از HttpClient برای هر درخواست باید یک نمونه از آن را در کل نرم افزار به اشتراک بگذاریم.
اجازه دهید با مثالی مطلب را دنبال کنیم:
یک برنامه کوتاه برای استفاده از کلاس HttpClient در زیر می بینید:

using System;
using System.Net.Http;

namespace ConsoleApplication
{
    class Program
    {
        public static void Main(string[] args) 
        {
            Console.WriteLine("Starting connections");
            for(int i = 0; i<10; i++)
            {
                using(var client = new HttpClient())
                {
                    var result = client.GetAsync("https://microsoft.com").Result;
                    Console.WriteLine(result.StatusCode);
                }
            }
            Console.WriteLine("Connections done");
        }

        
    }
}

همانطور که مشاهده می کنید قطعه کد بسیار ساده ای نوشته ایم که توسط بلاک using و با استفاده از کلاس HttpClient ده بار درخواست Get را برای سایت مایکروسافت ارسال می کند.
در تصویر زیر هم می توانید ببینید که برنامه به خوبی اجرا شده و ده ارسال با موفقیت پاسخ داده شده اند.

تا اینجا کار به صورت معمول پیش رفته و به خوبی انجام شد، اما اجازه بدهید با استفاده از ابزای Netstat سوکت های باز را قبل و بعد از اجرای برنامه بررسی کنیم:
قبل از اجرای برنامه سوکت های باز در سیستم بنده را در تصویر می بینید :

نکته قابل توجه لیست سوکت های باز بعد از اجرای برنامه (حداقل 5 ثانیه بعد از خاتمه یافتن کار برنامه) می باشد:

بله می توانید در تصویر ببینید که حتی بعد از اینکه کار برنامه به اتمام رسیده است باز هم 10 سوکت باز شده همچنان باز هستند و بسته نشده اند!
اگر دقت کنید وضعیت این اتصالات در حالت TIME_WAIT می باشد و این به این معنی می باشد که این اتصال در یک سمت بسته شده است (سمت ما) ولی در سمت دیگر همچنان باز است و منتظر دریافت پکیج های جدید می باشد (بخاطر اینکه ممکن است شبکه تاخیر داشته باشد و پکیج ها دیرتر به سرور برسند).
در تصویر زیر یک دیاگرام از وضعیت TCP/IP را می توانید ببینید که در سایت www4.cs.fau.de موجود است.

ویندوز اتصال را به مدت 240 ثانیه در حالت TIME_WAIT نگه می دارد (در آدرس [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay] تنظیم می شود) و مشکلی که داریم محدودیت ویندوز در سرعت اجرای سوکت جدید می باشد که اگر به این مشکل بر خورده باشید خطای زیر باید آشنا باشد:

Unable to connect to the remote server
System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.

جست و جو در باره این خطا در گوگل به شما خواهد گفت که timeout اتصالات را کم کنید!!!! در این مورد یک اشتباه بزرگ است! به این دلیل که کم کردن تایم اوت اتصالات اشکالات دیگری در HttpClient و دیگر بخش ها برای شما ایجاد خواهد کرد.
ما باید بدانیم دقیقا مشکل چیست و اصل مشکل را حل کنیم نه با تغییر متغیرهای سرور فقط روی مشکل سرپوش بذاریم!

-راه حل

اگر یک بار نمونه ای از HttpClient بسازیم و استفاده کنیم می توانیم با استفاده مجد از سوکت ها از اتلاف آنها جلوگیری کنیم.
تغییراتی در کدهای بالا می دهیم و به صورت زیر می نویسیم :

using System;
using System.Net.Http;

namespace ConsoleApplication
{
    class Program
    {
        private static HttpClient Client = new HttpClient();
        public static void Main(string[] args) 
        {
            Console.WriteLine("Starting connections");
            for(int i = 0; i<10; i++)
            {
                var result = Client.GetAsync("https://microsoft.com").Result;
                Console.WriteLine(result.StatusCode);
            }
            Console.WriteLine("Connections done");
        }

        
    }
}

همانطور که می بینید از بلاک using استفاده نشده و بجای آن یک نمونه از HttpClient ساختیم و سپس برای هربار که حلقه در حال تکرار می باشد از همان نمونه استفاده می کنیم.
حالا اگر برنامه را اجرا کنیم می بینیم کار به همون شیوه قبل اجرا می شود و همان نتیجه را دریافت خواهیم کرد (با کمی سرعت بیشتر در اجرا بخاطر استفاده از یک نمونه HttpClient).
حالا اگر مجدد برنامه را اجرا کنیم و از ابزار Netstat استفاده کنیم خواهیم دید یک سوکت در حال استفاده می باشد.

دقت داشته باشید تمام این مطالب با مثالی بود که تنها برای آزمایش نوشته ایم، در پروژه ای که به این مشکل برخوردم حدود 4500 سوکت به طور متوسط استفاده می شد که در زمان اوج مصرف این عدد به 5000 تا 5500 میرسید که باعث ناپایداری شدید برنامه می شد.
با انجام این تغییرات کوچک در استفاده از HttpClient نتیجه بسیار شگفت انگیز بود، سوکت های مورد استفاده از حدود 4000 به تعداد 400 کاهش پیدا کردند که در بیشتر موارد حدود 100 سوکت در حال استفاده است!

نتیجه گیری:

استفاده از بلاک using بسیار مفید و کارا می باشد اما در رابطه با استفاده از کلاس HttpClient در بلاک using این کار بسیار اشتباه و مخرب است.
اگر شما هر حالتی از لود کردن با استفاده از HttpClient نیاز داشتید به خاطر داشته باشید :
1- نمونه HttpClient را به صورت static معرفی کنید.
2- از بلاک using استفاده نکنید!

 

اگر این مطلب برای شما مفید بوده آن را به اشتراک بگذارید تا دوستان شما هم استفاده کنند.

نظرات (1)

نظرسنجی
میزان رضایت شما از امکانات و مقالات سایت