我正在使用https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio中的后台任务队列
在我的startup.cs类中,添加了以下内容:
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
添加了API的所有其他服务,例如services.AddScoped<IUserService, UserService>();
然后在我的控制器中上载文件,将其发送到blob存储并返回OK()
状态,同时开始处理文件的队列。正常工作,直到在处理期间我想更新数据库中的值:
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);
}
}
调用存储库var user = await _userRepository.Get(userId)
/ await _uploadDataRepository.Update(uploadData)
失败
具有例外:
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()
}
});
}
控制器:
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();
}
}
为什么在使用队列时不能调用其他存储库以及如何解决该问题?
答案 0 :(得分:1)
在QueuedHostedService
构造函数中导入IServiceProvider
public QueuedHostedService(IServiceProvider serviceProvider){
_serviceProvider = serviceProvider;
}
然后在您的ImportFile
方法中从IServiceProvider
创建一个范围,然后使用该范围获取所需的服务。
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
}
}