- Singleton
- Scoped
- Transient
Singleton (یگانه)
فقط و فقط یک شیء از سرویس ثبت شده با این طول عمر، در اولین درخواست ایجاد میشود و سپس در کل طول حیات برنامه، از همین شیء ایجاد شده، استفاده میگردد. به همین دلیل به آن «یگانه» یا Singleton میگویند. هر زمانیکه این سرویس در خواست داده میشود، DI Container، همان یک شیء را در اختیار درخواست دهنده قرار میدهد و این شیء، هیچگاه از بین نمیرود. به بیان دیگر، DI Container هیچگاه این شیء را از بین نمیبرد. شیء ساخته شده از سرویس ثبت شدهی با حالت Singleton، بین تمامی استفاده کنندگان، به صورت اشتراکی استفاده میشود. این طول عمر تقریبا مشابهی اشیاء ساخته شده توسط Singleton Pattern عمل میکند.
با توجه به مطالب گفته شده، ویژگیهای سرویسهای Singleton به شرح زیر هستند:
- در اولین درخواست به سرویس، یک نمونه از آن ساخته میشود و تا پایان برنامه در حافظه نگه داشته میشود.
- در سایر درخواستها، همان یک نمونهی ساخته شدهی از سرویس، ارائه داده میشود.
- به علت موجود بودن در حافظه، معمولا دسترسی به آنها و عملکرد آنها سریعتر است.
- بار کاری بر روی Garbage Collector فریمورک را کاهش میدهند.
بنابراین در هنگام تعریف کردن یک سرویس به صورت Singleton باید نکات زیر را مد نظر قرار بدهید:
- باید سرویس مورد نظر Thread Safe باشد .
- نباید استفاده کنندهی از این سرویس، امکان تغییر State آن را داشته باشد.
- اگر ساخت شیءای از یک سرویس، هزینهی زیادی را داشته باشد ، احتمالا Singleton کردن آن میتواند ایدهی خوبی باشد.
- شیء ساخته شدهی از این سرویس، تا زمان اجرای برنامه، بخشی از حافظهی برنامه را اشغال میکند. پس باید حجم اشغالی در حافظه را نیز مد نظر قرار داد.
- تعداد دفعات استفاده را در برابر مصرف حافظه در نظر بگیرید.
services.AddSingleton(services => new GuidProvider());
public HomeController(ILogger<HomeController> logger, IMessageServiceA messageService, LiteDbConfig liteDbConfig, GuidProvider guidHelper) { _logger = logger; _messageService = messageService; _messageService = new MessageServiceAA(); _guidHelper = guidHelper; }
حالا اگر برنامه
را اجرا کنیم، میبینید که با تازه
سازی صفحهی Home/Index ، همچنان Id، برابر با یک رشتهی یکسان
است. حتی اگر تب دیگری را در مرورگر باز کنیم و دوباره به این صفحه برویم، میبینیم
که Id برابر همان
رشتهی قبلی است و دلیل این موضوع، ثبت
سرویس Guid Service به صورت Singleton است.
Scoped ( محدود شده )
به ازای هر درخواست (در اینجا معمولا درخواستهای Http مد نظر است) یک نمونه از این سرویس ساخته میشود و در طول حیات این درخواست، DI Container به هر کلاسی که به این سرویس نیاز دارد، همان یک نمونه را برگشت میدهد و این نمونه در کل طول اجرای این درخواست، بین تمامی سرویس گیرندگان، یکسان است. هر زمانی، درخواست به پایان برسد، نمونهی ساخته شده از سرویس، Disposed میگردد و GC میتواند آن را از بین ببرد.
معمولا سرویسهای اتصال به پایگاه دادهها و کار بر روی آنها که شامل خواندن، نوشتن، ویرایش، حذف میشوند را با طول حیات Scoped ، درون DI Container ثبت میکنند . EF Core به صورت پیش فرض ، Db Context را به صورت Scoped ثبت میکند.
سرویسهای Scoped در محدودهی درخواست، مانند Singleton عمل میکنند و شیء ساخته شده و وضعیت آن در بین تمامی سرویسهایی که به آن نیاز دارند، مشترک است. بنابراین باید به این نکته در هنگام تعریف سرویس به صورت Scoped ، توجه داشته باشید.
تمام Middleware های ASP.NET Core هم فقط همان نمونهی ایجاد شده از سرویس Scoped را در طی اجرای یک درخواست خاص، میگیرند.
هر سرویسی که به سرویسهای Scoped نیاز دارد، یا باید به صورت Transient و یا باید به صورت Scoped ثبت شود، تا مانع از این شویم که شیء ساخته شده، فراتر از طول حیات موردنظرمان، در حافظه بماند و از آن استفاده شود .
برای ثبت یک سرویس
به صورت Scoped میتوانیم از متدهای توسعهای با نام AddScoped() با سربارهای
مختلف بر روی IServiceCollection استفاده کنیم. در اینجا از نسخهای که دو
پارامتر جنریک را میگیرد، برای ثبت یک سرویس به صورت Scoped استفاده میکنیم:
services.AddScoped<IMessageServiceB, MessageServiceBA>();
می توانیم سرویس GuidProvider را به جای Signleton ، به صورت Scoped ثبت کنیم:
services.AddScoped(services => new GuidProvider());
Transient (گذرا)
به ازای هر درخواست دهندهی جدید، یک نمونهی جدید از سرویس، توسط DI Container ساخته میشود و در اختیار آن قرار میگیرد.
سرویسهایی را به این صورت ثبت کنید که:
- نیاز به Thread Safe بودن داشته باشند.
- نمی توانید طول عمر سرویس را حدس بزنید.
سرویسهای Transient ، کارآیی پائینتری دارند و سربار عملکردی زیادی بر روی Garbage Collector می گذارند؛ ولی به علت اینکه به ازای هر واکشی، یک نمونهی جدید از آنها ساخته میشود و State بین این اشیاء به اشتراک گذاشته نمیشود، امنیت بیشتری دارند و درک و خطایابی آنها سادهتر است.
برای ثبت سرویسهای Transient از متد توسعهای AddTransient() استفاده میکنیم. سربارهای این متد مانند سربارهای متدهای AddSingleton() و AddScoped() است:
services.AddTransient<IMessageServiceC, MessageServiceCA>();
وابستگیهای محصور شده
یک سرویس نباید وابستهی به سرویسی باشد که طول حیاتی کمتر از طول حیات خودش را دارد.
برای مثال اگر درون سرویسی با طول حیات Singleton، از یک سرویس با طول حیات Transient استفاده کنیم، اینکار باعث میشود که یک نمونه از سرویس Transient در طول حیات برنامه، همیشه درون حافظه بماند و این ممکن است باعث خطاهای عجیبی در هنگام اجرا شود که معمولا خطایابی و رفع آنها مشکل است.
اثرات جانبی وابستگیهای محصور شده:
- به اشتراک گذاری تصادفی وضعیت یک شیء بین Thread ها درون سرویسهایی که Thread Safe نیستند.
- اشیاء، بیش از زمان پیش بینی شدهی برایشان، درون حافظه باقی میمانند.
سرویسهای Transient میتوانند به سرویسهایی با طول حیات زیر وابستگی داشته باشند:
- Transient
- Scoped
- Singleton
سرویسهای Scoped میتوانند به سرویسهایی با طول حیات زیر وابستگی داشته باشند:
- Transient
- Scoped
سرویسهای Singleton میتوانند به سرویس هایی با طول حیات زیر وابستگی داشته باشند:
Singleton
میتوانید از جدول زیر به عنوان راهنمای خلاصه شدهی برای استفادهی امن از سرویسها درون یکدیگر بهره ببرید:
Scope Validation
این قابلیت که به صورت پیش
فرض در حالت توسعهی برنامههای ASP.NET Core فعال است، در زمان شروع برنامه و در Startup ، بررسی میکند که سرویسها، وابستگی
به سرویسهایی با طول حیاتی مناسب، داشته باشند.