اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
سه دقیقه
در قسمت قبل دیدیم که انجام کارهای همزمان، با Objectهایی که به اصطلاح Thread Safe نیستند (مانند DbContext) خروجی چندان جالبی ندارد و برای مثال اگر در یک Service یک DbContext را Inject کنیم (مثلا با Constructor injection) و از آن در متدی استفاده کنیم که آن متد یا با TPL یا RX و ... به صورت چندتایی و همزمان اجرا شود، DbContext به مشکل میخورد؛ یعنی نمیتوان یک وهله از DbContext را بین چند Thread همزمان پردازش موازی، به اشتراک گذاشت.
در کدهای فرضی مثالهای قسمت قبل، متدی داشتیم با نام DoSomethingWithCustomer که مثلا همان متدی بود که قرار است همزمان اجرا شود. یکی از سادهترین کارهایی که برای رفع این مشکل میتوان انجام داد، نوشتن چنین کدی است:
public async Task DoSomethingWithCustomer(Customer customer) { using var dbContext = new AppDbContext(); // ... }
این روش ابدا توصیه نمیشود؛ برای اینکه Dependency Injection این روزها مسائل خیلی زیادی را مدیریت میکند. مثلا DbContext در EF Core وقتی با Dependency Injection ساخته شود، Logging اش هم فعال میشود و یا مثلا اگر از متد زیر
services.AddDbContextPool<AppDbContext>();
و یا مثلا برای HttpClient فقط در همین سایت نزدیک به یک دوجین مقاله توضیح دادهاند که چرا new کردن و Dispose کردن HTTP Client مناسب نیست و بهتر است برای Register کردن HttpClient، از services.AddHttpClient استفاده و از IHttpClientFactory و سایر روشها برای Resolve کردن HttpClient استفاده کنیم که اینها نیز توسط Dependency Injection قابل استفاده هستند. از مسائلی مانند Polly و ... نیز میگذریم.
راه حل ایجاد یک Context جدید، تقریبا در تمامی کتابخانههای Dependency Injection دیده شدهاست و آن ساختن یک Child Scope است. در ادامه با Microsoft.Extensions.DependencyInjection یک پیاده سازی آنرا خواهیم داشت؛ ولی مشابه این روش در سایر کتابخانهها همچون Autofac نیز شدنی است.
برای شروع System.IServiceProvider را inject کنید. سپس کد قبلی را به این شکل بنویسید:
(نیاز به ;using Microsoft.Extensions.DependencyInjection در بالای فایل کد است)
public async Task DoSomethingWithCustomer(Customer customer) { using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var httpClient = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(); // ... }
همچنین میتوانید برای داشتن کدی بهتر، یک interface و class را ایجاد کنید و logic مربوطه را در آن سرویس قرار دهید و در آن سرویس با constructor یا property injection از DbContext و HttpClient و سایر سرویسها استفاده کنید و در نهایت آن interface/class را رجیستر کنید و در DoSomethingWithCustomer به کمک child scope، یک object از آن سرویس بسازید و متدش را فراخوانی کنید. برای مثال اگر هدف ساختن Excel تاریخچه خریدهای مشتری است، داریم:
public interface IOrderHistoryService { Task BuildCustomerHistory(); } public class OrderHistoryService : IOrderHistoryService { private AppDbContext _dbContext; public OrderHistoryService(AppDbContext dbContext) { _dbContext = dbContext; } public async Task BuildCustomerHistory() { // ... } }