在我的MVC应用程序中,超级管理员可以设置任务队列,例如更新数据库。因此,当管理员向队列添加更新时,控制器会启动在后台运行的新任务。但是,当您添加一些任务时,应用程序将抛出System.Threading.ThreadAbortException: Thread was being aborted
。此外,堆栈跟踪表明它发生在代码中的不同行。
我还应该补充说,任务使用EF6实体来使用sql-server,并且根据堆栈跟踪,它发生在对数据库执行操作之后或同时。由于更新通常很大,我使用db.Configuration.AutoDetectChangesEnabled = false
并手动保存每20k行的更改,处理并重新创建数据库。
堆栈跟踪示例:
2015年7月15日星期三下午5:18:36:[REPORT]异常(行:456667;第6节):System.Threading.ThreadAbortException:线程正在中止。 在System.Array.Copy(Array sourceArray,Int32 sourceIndex,Array destinationArray,Int32 destinationIndex,Int32 length,Boolean reliable) 在System.Collections.Generic.List`1.set_Capacity(Int32 value) 在System.Data.Entity.Core.Metadata.Edm.MetadataCollection`1.SetReadOnly() 在System.Data.Entity.Core.Metadata.Edm.TypeUsage..ctor(EdmType edmType,IEnumerable`1 facets) 在System.Data.Entity.Core.Common.CommandTrees.DbExpression..ctor(DbExpressionKind kind,TypeUsage类型,布尔forceNullable) 在System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.PropertyFromMember(DbExpression实例,EdmMember属性,String propertyArgumentName) 在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler.GenerateEqualityExpression(DbExpressionBinding target,EdmProperty属性,PropagatorResult值) 在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler.BuildPredicate(DbExpressionBinding target,PropagatorResult referenceRow,PropagatorResult current,TableChangeProcessor processor,Boolean& rowMustBeTouched) 在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler.BuildUpdateCommand(PropagatorResult oldRow,PropagatorResult newRow,TableChangeProcessor processor) 在System.Data.Entity.Core.Mapping.Update.Internal.TableChangeProcessor.CompileCommands(ChangeNode changeNode,UpdateCompiler编译器) 在System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.d__a.MoveNext() 在System.Linq.Enumerable.d__71`1.MoveNext() 在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCommandOrderer..ctor(IEnumerable`1命令,UpdateTranslator转换器) 在System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.ProduceCommands() 在System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update() 在System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction [T](Func`1 func,IDbExecutionStrategy executionStrategy,Boolean startLocalTransaction,Boolean releaseConnectionOnSuccess) 在System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions选项,IDbExecutionStrategy executionStrategy,Boolean startLocalTransaction) 在System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute [TResult](Func`1操作) at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options,Boolean executeInExistingTransaction) 在System.Data.Entity.Internal.InternalContext.SaveChanges() 在MyWebsite.Controllers.AdminPanelController.ApplyUpdate(String filePath,HttpApplicationStateBase context,Int32 saveInterval,Boolean checkRepetitions,String onCollision)
有什么我可以做错的吗?
答案 0 :(得分:4)
我猜它可能与IIS空闲状态有关。您可以尝试让IIS运行后台线程,每隔几分钟使用另一个空闲线程进行自我请求。它将阻止IIS中止你的另一个长时间运行的线程。每10分钟进行一次自我请求的方法示例:
public void KeepIisBackgroundThreadsAlive(Object stateInfo) {
while (true) {
var serverOwnIp = Dns.GetHostEntry(Dns.GetHostName()).AddressList.First(o => o.AddressFamily == AddressFamily.InterNetwork).ToString();
var req = (HttpWebRequest) WebRequest.Create(new Uri("http://" + serverOwnIp));
req.Method = "GET";
var response = (HttpWebResponse) req.GetResponse();
var respStream = response.GetResponseStream();
var delay = new TimeSpan(0, 0, 10, 0);
Thread.Sleep(delay);
}
}
然后你可以使用Global.asax.cs OnApplicationStarted方法中的ThreadPool.QueueUserWorkItem方法启动这个方法:
protected override void OnApplicationStarted(){
ThreadPool.QueueUserWorkItem(KeepIisBackgroundThreadsAlive)
}
重要更新
发现此方法仅适用于已禁用的IIS应用程序池回收。如果您不熟悉此IIS功能,请仔细阅读this文章。 请注意,您应该100%确定自己在做什么。
如果您使用此方法而不关闭回收,您可能会面临网站在第一次回收发生后不会启动严重错误。对于需要应用程序池回收的这些网站,我强烈建议将长时间运行的作业转移到不依赖于IIS的单独服务中(如果您使用Azure Web角色 - 至少应该使用单独的Worker Role实例)。
答案 1 :(得分:2)
我认为从长远来看,通过将此功能正确地抽象到另一个应用程序(更具体地说是Windows服务),您将获得更多积极成果。在我的公司,我写了一个长跑者/投票工作者的守护进程,它已经成为我们技术堆栈的重要组成部分已有4年多了。 它可能看起来像是工作日志,但它会让你收回股息。
对于你所面临的实际问题,顺便说一句;我同意@Vova的说法,你的应用程序是在IIS中托管的,IIS会做很多事情来确保你的应用程序不会关闭服务器的其余部分。其中一些可能包括终止线程。以下是一些人们在谈论你的问题的链接(谷歌有点,你会发现更多):
修改强> 我认为我应该为那些感兴趣的人详细介绍这个Daemon服务的架构。
基本上它不是太复杂,但非常有效。它由做“东西”的“工人”类组成。负载平衡类管理所有工作实例并调用它们来完成一部分工作,因此负载均衡器可以跟踪工作者上次做某事的时间并告诉他们做另一个块,同时大致确保服务器没有受到太大的负担。这可能听起来很棘手,但我保持相当简单。很酷的部分是每个工作者定义一种处理方式,可以是:
负载均衡器将每个工作人员的状态存储在MongoDB记录中,以便该进程能够容忍失败......尽管每个工作者基类确保处理,记录和通过电子邮件发送异常(尽管这并不意味着内存泄漏之类的东西无法降低进程/机器。)
要考虑的最后一个方面是如何将工人状态反馈给人类并实现人工干预(暂停,强制运行等)。我通过公开非常轻量级的WCF服务来做到这一点,其中只有2个:
我们的管理门户使用第一个服务来创建一个页面,该页面自动轮询(使用KnockoutJS)回到管理网站,然后回到守护进程。
我们发现这是一个非常好(但轻量级且简单)的后端处理套件,可处理各种任务Solr记录更新,报告,数据挖掘,文件库清理等等。