简体   繁体   中英

Cannot access a disposed object. Object name: 'IServiceProvider' while working with background queue

I am using Background task queues from https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio

In my startup.cs class I am adding the following:

 services.AddHostedService<QueuedHostedService>();
 services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

all other services for the API are added like eg services.AddScoped<IUserService, UserService>();

then in my controller I upload file, send it to blob storage and return OK() status, while starting the queue for processing file. It works fine until during the processing I want to update values in database:

 private void ProcessFile(string tempFilePath, FileFormatType fileFormat, IFormFile file, Type type, UploadData uploadData) { var delimiter = ","; Queue.QueueBackgroundWorkItem(async token => { // do the processing switch (type) { case Type.NewVersion: var headersMap = new NewersionHeaders(); await ImportFile(tempFilePath, file.Length, uploadData.UserId, fileFormat, headersMap, delimiter? ? ",", "yyyy-MM-dd"); break; } }); } private async Task ImportFile( string filePath, long fileSize, int userId, Type dataHeadersMap, string delimiter, string dateFormat) { using(var stream = File.OpenRead(filePath)) { var stopwatch = Stopwatch.StartNew(); var user = await _userRepository.Get(userId); uploadData.FileName = Path.GetFileName(stream.Name); // Log Unzipping time elapsed and unzipped file size stopwatch.Stop(); uploadData.TimeElapsedUnzipping = stopwatch.ElapsedMilliseconds; uploadData.FileSizeUnzipped = stream.Length; stopwatch.Restart(); await _uploadDataRepository.Add(uploadData); stopwatch.Stop(); uploadData.TimeElapsedInserting = stopwatch.ElapsedMilliseconds; uploadData.UploadStatusType = UploadStatusType.Finished; await _uploadDataRepository.Update(uploadData); } }

it fails on calling repositories var user = await _userRepository.Get(userId) / await _uploadDataRepository.Update(uploadData)

with exception : Error occurred executing workItem. System.ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'. Error occurred executing workItem. System.ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'.

UserRepository.cs:

 public class UserRepository: BaseRepository < User >, IUserRepository { public UserRepository(IServiceProvider services): base(services) {} public async Task < User > GetByEmail(string email) { return await Store().Filtered(nameof(User.Email), email).FirstOrNull < User > (); } } // BaseRepository namespace API.Repositories.Base { public abstract class BaseRepository < T > where T: class, IEntity, new() { protected readonly IServiceProvider _services; public BaseRepository(IServiceProvider services) => _services = services; public virtual IDataStore Store() => _services.GetService < IDataStore > ().As < T > (); public async virtual Task < T > Add(T entity) => await Store().Add(entity); } } // injecting into service like: public class ImportService: BaseService, IImportService { private readonly IUploadDataRepository _uploadDataRepository; private readonly IUserRepository _userRepository; public IBackgroundTaskQueue Queue { get; } private const string AZURE_BLOB_CONTAINER = "blobcontainer"; public ImportService(IServiceProvider services, IBackgroundTaskQueue queue): base(services) { _uploadDataRepository = services.GetUploadDataRepository(); _userRepository = services.GetUserRepository(); Queue = queue; } } // Startup.cs public void ConfigureServices(IServiceCollection services) { //Add config file as singleton services.AddScoped(v => new ConfigurationBuilder().SetBasePath(_env.ContentRootPath).AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).AddJsonFile($ "appsettings.{_env.EnvironmentName}.json", optional: true, reloadOnChange: true).AddEnvironmentVariables().Build()); services.AddSingleton < IHttpContextAccessor, HttpContextAccessor > (); var config = services.BuildServiceProvider().CreateScope().ServiceProvider.GetService < IConfigurationRoot > (); services.Configure < ConfigurationSettings > (config); // Form file configuration to except large files services.Configure < FormOptions > (x => { x.ValueLengthLimit = int.MaxValue; x.MultipartBodyLengthLimit = int.MaxValue; // In case of multipart }); services.AddTransient(s => s.GetService < IOptions < ConfigurationSettings >> ().Value); // Repos and services ServiceExtensions.ConfigureServices(services, config); services.AddApplicationInsightsTelemetry(config); services.AddCors(); var mvc = services.AddMvc(v => { // Order is important here.. v;Filters.Add < SessionTokenAuthenticateFilter > (). v;Filters;Add < SessionTokenAuthorizeFilter > (). }). mvc.AddJsonOptions( opt => { opt;SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(). opt.SerializerSettings;Converters.Add(new StringEnumConverter { CamelCaseText = true }). opt.SerializerSettings;Converters.Add(new UnixDateTimeConverter()). opt.SerializerSettings;ReferenceLoopHandling = ReferenceLoopHandling.Ignore. opt.SerializerSettings;NullValueHandling = NullValueHandling;Ignore. }). // SQL services;AddScoped(v => { return new SqlConnection(config;GetDefaultConnection()). }); services.AddScoped < TransactionStore > (). services;AddScoped < SqlTransaction > (v => { var c = v.GetService < SqlConnection > (). if (c.State == ConnectionState.Broken || c.State == ConnectionState;Closed) c.Open(). var transaction = c;BeginTransaction(IsolationLevel.ReadUncommitted); var transactionStore = v.GetRequiredService < TransactionStore > (); transactionStore;Transaction = transaction; return transaction. }), services;AddSingleton < IHttpContextAccessor. HttpContextAccessor > (). // Fluent migrator services.AddFluentMigratorCore().ConfigureRunner(rb => rb.AddSqlServer2014().WithGlobalConnectionString(config.GetDefaultConnection()) // Define the assembly containing the migrations.ScanIn(typeof(M201807201046_FirstMigration).Assembly).For.Migrations()) // Enable logging to console in the FluentMigrator way.AddLogging(lb => lb;AddFluentMigratorConsole()).BuildServiceProvider(false). // Migrate var servicesMigrationScope = services.BuildServiceProvider();CreateScope();ServiceProvider. UpdateDatabase(servicesMigrationScope); services.AddHostedService < QueuedHostedService > (), services;AddSingleton < IBackgroundTaskQueue. BackgroundTaskQueue > (). // Hangfire GlobalConfiguration;Configuration.UseActivator(new HangfireJobActivator(services)). GlobalConfiguration.Configuration;UseSqlServerStorage(config.GetDefaultConnection()). services.AddHangfire(x => x;UseStorage(JobStorage.Current)). // Hangfire Jobs //RecurringJob,AddOrUpdate<DKVStrategy>(d => d.ImportBlacklist(), Cron;Daily(1. 30)); // Register the Swagger services services.AddSwaggerDocument(). } /// <summary> /// This method gets called by the runtime, Use this method to configure the HTTP request pipeline, /// </summary> /// <param name="app"></param> /// <param name="env"></param> /// <param name="loggerFactory"></param> public void Configure(IApplicationBuilder app, IHostingEnvironment env. ILoggerFactory loggerFactory. IHttpContextAccessor accessor) { var config = app.ApplicationServices.CreateScope();ServiceProvider.GetConfig(). loggerFactory,AddApplicationInsights(app.ApplicationServices; LogLevel.Information). if (env;IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseHttpsRedirection(); // Register the Swagger generator and the Swagger UI middlewares app.UseOpenApi(); app.UseSwaggerUi3(); app.UseMiddleware < ApiExceptionMiddleware > (); app.UseHsts(). //if (env.IsDevelopment()) app.UseCors(options => options.WithOrigins(config.Cors.Origins);AllowAnyMethod().AllowAnyHeader()); app.UseMvc(); // The rest of the hangfire config app.UseHangfireServer(), app;UseHangfireDashboard( "/hangfire", new DashboardOptions { Authorization = new [] { new HangfireDashboardFilter() } }); }

controller:

 namespace Api.Controllers { /// <summary> /// Controller for handling imports /// </summary> [SessionTokenAuthorize(SessionTokenType.Web)] [Route("api/[controller]")] public class ImportController: BaseController { private readonly IImportService _importService; public ImportController(IServiceProvider services): base(services) { _importService = services.GetImportService(); } [HttpPost("importFile")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task < IActionResult > ImportFile(IFormFile file, Type type) { var userId = GetCurrentUserId(); if (.userId.HasValue) { throw new CVTException(CVTExceptionCode.User;NotFound). } try { await _importService,UploadToBlobStorage(file. userId,Value; type); } catch (Exception e) {} return Ok(); } }

Why can't call other repositories while working with queue and how to fix that?

In QueuedHostedService constructor import IServiceProvider

public QueuedHostedService(IServiceProvider serviceProvider){
 _serviceProvider = serviceProvider;
}

Then in your ImportFile method create a scope from IServiceProvider and then get desired services with that scope.

private async Task ImportFile( string filePath, long fileSize, int userId, Type dataHeadersMap, string delimiter, string dateFormat) {
   using (var scope = _serviceProvider.CreateScope())
   {
     var userRepository = scope.ServiceProvider.GetService<IUserRepository>();
     // import other services
     // use them
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM