2

I am using .NET Core 3.1. I want to run some background processing without user having to wait for it to finish (it takes about 1 minute). Therefore, I used Task.Run like this:

public class MyController : Controller { private readonly IMyService _myService; public MyController(IMyService myService) { _myService = myService; } public async Task<IActionResult> Create(...) { await _myService.CreatePostAsync(...); return View(); } } public class MyService : IMyService { private readonly MyDbContext _dbContext; private readonly IServiceScopeFactory _scopeFactory; public MyService(MyDbContext dbContext, IServiceScopeFactory scopeFactory) { _dbContext = dbContext; _scopeFactory = scopeFactory; } public async Task CreatePostAsync(Post post) { ... string username = GetUsername(); DbContextOptions<MyDbContext> dbOptions = GetDbOptions(); Task.Run(() => SaveFiles(username, dbOptions, _scopeFactory)); } private void SaveFiles(string username, DbContextOptions<MyDbContext> dbOptions, IServiceScopeFactory scopeFactory) { using (var scope = scopeFactory.CreateScope()) { var otherService = scope.ServiceProvider.GetRequiredService<IOtherService>(); var cntxt = new MyDbContext(dbOptions, username); Post post = new Post("abc", username); cntxt.Post.Add(post); <----- EXCEPTION cntxt.SaveChanges(); } } } 

I recieve the following exception in marked line:

System.ObjectDisposedException: 'Cannot access a disposed object. Object name: 'IServiceProvider'.' 

Why does this happen? I used custom constructor (and not scope.ServiceProvider.GetRequiredService<MyDbContext>()) for MyDbContext because I need to save one additional propery (username) for later use in overriden methods.

public partial class MyDbContext { private string _username; private readonly DbContextOptions<MyDbContext> _options; public DbContextOptions<MyDbContext> DbOptions { get { return _options; } } public MyDbContext(DbContextOptions<MyDbContext> options, string username) : base(options) { _username = username; _options = options; } ... other overriden methods } 

What am I doing wrong?

4
  • what is MyService usage ? Is it created in the controller? Asp.Net creates new container scope for each incoming request. It means that container from the scope is disposed when controller's method returns the result. This explains the IServiceProvider disposed error you have. This may cause ctor object instances to be disposed by the container while certain task continues to use those instances. Commented Apr 9, 2020 at 7:25
  • Yes, MyService is injected in the controller constructor. Controller then calls await _myService.CreatePostAsync(...). What should I do to avoid this error? Commented Apr 9, 2020 at 7:29
  • I updated the question so that it includes controller part as well. Commented Apr 9, 2020 at 7:32
  • You should pretty much never use Task.Run in a web app. There is no point. It allows the current thread to be returned to the pool, but simply pulls out another in its place from that same pool. Since all requests are serviced from that same threadpool, all you're doing is cutting your total available request throughput. Commented Apr 9, 2020 at 14:49

2 Answers 2

8

First of all, don't hide a thread-pool operation away in your service; let the calling coded decide whether to run the operation on the thread-pool or not:

As you are using dependency injection, the framework is disposing your DbContext at the end of the HTTP request.

You need to inject your service scope factory into your controller, and request the service from there:

public class MyController : Controller { private readonly IMyService _myService; private readonly IServiceScopeFactory _scopeFactory; public MyController(IMyService myService, IServiceScopeFactory scopeFactory) { _myService = myService; _scopeFactory = scopeFactory; } public async Task<IActionResult> Create(...) { HostingEnvironment.QueueBackgroundWorkItem(SaveInBackground); return View(); } private async Task SaveInBackground(CancellationToken ct) { using (var scope = scopeFactory.CreateScope()) { var scopedService = scope.ServiceProvider.GetRequiredService<IMyService>(); await scopedService.CreatePostAsync(...); } } } 

HostingEnvironment.QueueBackgroundWorkItem works in a similar way to Task.Run, except it ensures that the app doesn't shut down until all background work items have completed.

Your service would need to be something like this:

public class MyService : IMyService { private readonly MyDbContext _dbContext; public MyService(MyDbContext dbContext) { _dbContext = dbContext; } public async Task CreatePostAsync(Post post) { _dbContext.Post.Add(post); await _dbContext.SaveChangesAsync(); } } 

UPDATE

To pass additional parameters to SaveInBackground:

private async Task SaveInBackground(YourParam param) 

Then call like:

HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => SaveInBackground(yourParam)); 
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! How can I pass additional parameter to SaveInBackground (ex. model which I want to save to database)?
HostingEnvironment.QueueBackgroundWorkItem is not available in .NET Core 3. What should I use instead?
-2

You shoud create a Service with a Singleton lifecycle and inject a DBContext inside and queue all tasks inside

3 Comments

Injecting a scoped dependency (such as a DbContext) into a singleton is in general a bad idea. If you think that it is a good approach in this particular case, you should explain why that is, and why you won't run into concurrency (and other) issues.
I'm okay with the fact that a global DBContext is a terrible idea, that why i suggest to queue tasks to avoid the concurency issue
There are other issues with global DbContext instances besides concurrency, such as that their data becomes stale (as noted in the referenced answer). And you might be fine with all this, but in the very least you should warn the inattentive or less experienced reader of your answer about this.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.