场景描述:
单位项目目前使用Masstransit rabbitmq 做发布订阅
订阅方接收到消息后会做一些增删改动作
数据操作用的EntityFramworkCore,DbContext使用Autofac注入的,范围是InstancePerLifetimeScope
问题:
目前发现如果出现并发的情况,数据增删改会出现异常
System.InvalidOperationException
A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
代码:
发布方
public CommonOutputDto SystemLoginOut(SystemLoginOutRequest systemLoginOutRequest) { var result = new CommonOutputDto() { IsSuccess = true }; foreach (var distributorID in systemLoginOutRequest.Distributors) { var model = _ssoAuthTokenRepository.Table.Where(x => x.DISTRIBUTOR_ID.Equals(distributorID)); if (model.Count() > 0) { _ssoAuthTokenRepository.Delete(model); } //向mq发送信息 _busControl.Publish(new SSOAuthTokenConsumer() { DistributorID = distributorID, tokenConsumerType = ToKenConsumerType.DeleteAll }); } return result; }
订阅方
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var appSettings = Configuration.Get<AppSettings>(); if (appSettings.tokenConfig.AuthCenterType == AuthCenterType.Slave) {
//订阅 var _busControl = BusConfiguration.CreateBus(appSettings, (cfg, host) => cfg.ReceiveEndpoint(host, appSettings.rabbitMQConfig.QueueName, e => { e.Consumer<ToKenConsumer>(); })); applicationLifetime.ApplicationStarted.Register(_busControl.Start); applicationLifetime.ApplicationStopped.Register(_busControl.Stop); } app.SwaggerConfigBuilder(); app.UseMiddleware(typeof(ApiErrorHandlingMiddleware)); app.UseMvc(); app.SwaggerConfigBuilder(); }
TokenConsumer实现 注:lock是我的临时解决办法 但我还是想找到问题所在
public class ToKenConsumer : IConsumer<SSOAuthTokenConsumer> { private static readonly object ConsumerLock = new object(); public Task Consume(ConsumeContext<SSOAuthTokenConsumer> context) { lock (ConsumerLock) { try { var ssoService = EngineContext.Current.Resolve<ISSOService>(); ssoService.ResetSlaveToken(context.Message); } catch (Exception ex) { LogHelper.Error(context.Message.tokenConsumerType.ToString() + " " + ex.Message, ex); } return Task.CompletedTask; } } }
注入的DBContext
builder.RegisterType<OracleContext>().As<IDbContext>().InstancePerLifetimeScope();
ssoService的实现
private readonly IRepository<SSOAuthToken> _ssoAuthTokenRepository; private readonly IDbContext _dbContext; public SSOService( IDbContext dbContext, IRepository<SSOAuthToken> ssoAuthTokenRepository) { this._dbContext = dbContext; this._ssoAuthTokenRepository = ssoAuthTokenRepository; } public void ResetSlaveToken(SSOAuthTokenConsumer ssoAuthTokenConsumer) { switch (ssoAuthTokenConsumer.tokenConsumerType) { case ToKenConsumerType.Add: { var model = new SSOAuthToken() { ID = GetSeqID(), CREATE_DATE = ssoAuthTokenConsumer.ssoAuthToken.CREATE_DATE, DISTRIBUTOR_ID = ssoAuthTokenConsumer.ssoAuthToken.DISTRIBUTOR_ID, TOKEN = ssoAuthTokenConsumer.ssoAuthToken.TOKEN, TOKEN_TYPE = ssoAuthTokenConsumer.ssoAuthToken.TOKEN_TYPE }; _ssoAuthTokenRepository.Insert(model); break; } case ToKenConsumerType.Update: { var token = ssoAuthTokenConsumer.ssoAuthToken.TOKEN; var did = ssoAuthTokenConsumer.ssoAuthToken.DISTRIBUTOR_ID; var model = _ssoAuthTokenRepository.Table.Where(x => x.TOKEN.Equals(token) && x.DISTRIBUTOR_ID.Equals(did)).FirstOrDefault(); if (model != null) { model.TOKEN = ssoAuthTokenConsumer.ssoAuthToken.TOKEN; model.DISTRIBUTOR_ID = ssoAuthTokenConsumer.ssoAuthToken.DISTRIBUTOR_ID; model.CREATE_DATE = ssoAuthTokenConsumer.ssoAuthToken.CREATE_DATE; model.TOKEN_TYPE = ssoAuthTokenConsumer.ssoAuthToken.TOKEN_TYPE; _ssoAuthTokenRepository.Update(model); } break; } case ToKenConsumerType.Delete: { var token = ssoAuthTokenConsumer.ssoAuthToken.TOKEN; var did = ssoAuthTokenConsumer.ssoAuthToken.DISTRIBUTOR_ID; var model = _ssoAuthTokenRepository.Table.Where(x => x.TOKEN.Equals(token) && x.DISTRIBUTOR_ID.Equals(did)).FirstOrDefault(); if (model != null) { _ssoAuthTokenRepository.Delete(model); } break; } case ToKenConsumerType.DeleteAll: { var did = ssoAuthTokenConsumer.DistributorID; var model = _ssoAuthTokenRepository.Table.Where(x => x.DISTRIBUTOR_ID.Equals(did)).ToList(); if (model.Count() > 0) { _ssoAuthTokenRepository.Delete(model); } break; } } ResetRedisToken(ssoAuthTokenConsumer.DistributorID); }
请各路大神帮我看看 为什么会报这个错误 我百度了很久 但都是介绍"context before a previous asynchronous operation" 这个错误,可是我并没有用异步。
拜托各位了 新注册的号 还没有豆豆 - -!
ssoService
是不是被注册为单例了?
园主 你感觉是这句的问题 "var ssoService = EngineContext.Current.Resolve<ISSOService>();" ?
@232111: 代码中如何注册 ISSOService 的?
@232111: 建议提供一下依赖注入部分的代码
@dudu:
注入类里是这么写的
builder.RegisterType<SSOService>().As<ISSOService>().InstancePerLifetimeScope();
但是我在TokenConsumer里并不是通过构造函数使用 ,而是new出来的,因为Consumer构造函数不允许有参数
var ssoService = EngineContext.Current.Resolve<ISSOService>();
@232111: IRepository<SSOAuthToken> 是如何注册的?
@dudu:
builder.RegisterGeneric(typeof(EfRepository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();
@232111: 出现这个问题是由于通一个 DbContext 实例被多个线程使用,建议把 InstancePerLifetimeScope
都改为 InstancePerHttpRequest
试试
@dudu: 嗯 谢谢园主 我试试 再问个问题 我第一次使用autofac 一直以为InstancePerLifetimeScope的作用域就是当前请求的上下文 不对吗?
@232111: 我对 autofac 也不熟悉,我们都用的是 .net core 内置的依赖注入,InstancePerHttpRequest 与 InstancePerLifetimeScope 的区别详见 Autofac - InstancePerHttpRequest vs InstancePerLifetimeScope
@dudu:
园主,我的问题按你这篇文章解决的
https://www.cnblogs.com/dudu/p/9353369.html
谢谢