并行任务中的System.Threading.ThreadAbortException

本文关键字:Threading ThreadAbortException System 并行任务 | 更新日期: 2023-09-27 18:03:17

在我的MVC应用程序中,超级管理员可以设置一个任务队列,例如更新数据库。因此,当管理员向队列添加更新时,控制器启动一个在后台工作的新任务。但是,当您添加一些任务时,应用程序会抛出System.Threading.ThreadAbortException: Thread was being aborted。此外,堆栈跟踪表明它发生在代码的不同行上。

我还应该补充说,任务使用EF6实体与SQL-server一起工作,并且根据堆栈跟踪,它发生在对数据库执行操作之后或同时。由于更新通常很大,所以我使用db.Configuration.AutoDetectChangesEnabled = false并手动保存每20k行更改,处理并重新创建数据库。

堆栈跟踪示例:

2015年7月15日星期三下午5:18:36:[报告]异常(Line:456667;Section6): System.Threading.ThreadAbortException:线程被中止。在System.Array。复制(数组sourceArray, Int32 sourceIndex,数组destinationArray, Int32 destinationIndex, Int32长度,布尔可靠)在System.Collections.Generic.List 1。set_Capacity (Int32价值)1. System.Data.Entity.Core.Metadata.Edm.MetadataCollection"setreadonly ()在System.Data.Entity.Core.Metadata.Edm.TypeUsage . .////////////在System.Data.Entity.Core.Common.CommandTrees.DbExpression . .(类型类型,类型类型,布尔值)在System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder。属性frommember (dbeexpression实例,EdmMember属性,String propertyArgumentName)在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler。GenerateEqualityExpression(dbeexpression绑定目标,EdmProperty属性,PropagatorResult值)在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler。BuildPredicate(dbeexpression binding target, PropagatorResult referenceRow, PropagatorResult current, TableChangeProcessor处理器,Boolean&rowMustBeTouched)在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler。buildpdatcommand (PropagatorResult oldRow, PropagatorResult newRow, TableChangeProcessor processor)在System.Data.Entity.Core.Mapping.Update.Internal.TableChangeProcessor。编译命令(ChangeNode, ChangeNode, UpdateCompiler编译)在System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.d__a.MoveNext ()1. System.Linq.Enumerable.d__71"movenext ()在System.Data.Entity.Core.Mapping.Update.Internal.UpdateCommandOrderer . .(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 function, idbeexecutionstrategy, executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)在System.Data.Entity.Core.Objects.ObjectContext。SaveChangesToStore(SaveOptions options, idbeexecutionstrategy, executionStrategy, Boolean startLocalTransaction)在System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy。执行[TResult] (Func ' 1操作)在System.Data.Entity.Core.Objects.ObjectContext。SaveChangesInternal(SaveOptions选项,Boolean executeInExistingTransaction)在System.Data.Entity.Internal.InternalContext.SaveChanges ()在MyWebsite.Controllers.AdminPanelController。ApplyUpdate(字符串filePath, HttpApplicationStateBase上下文,Int32 saveInterval, Boolean checkRepetitions,字符串onCollision)

我能做错什么吗?

并行任务中的System.Threading.ThreadAbortException

我猜可能与IIS空闲状态有关。您可以尝试让IIS运行后台线程,使用每隔几分钟从另一个空闲线程发出的基于自的请求。它将防止IIS中止您的另一个后台长时间运行的线程。方法示例:每10分钟做一次self请求:

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);
    }
}

那么你可以使用ThreadPool来启动这个方法。OnApplicationStarted方法:

protected override void OnApplicationStarted(){
    ThreadPool.QueueUserWorkItem(KeepIisBackgroundThreadsAlive)
}

重要更新

发现此方法仅适用于禁用IIS应用程序池回收。如果您不熟悉这个IIS特性,请仔细阅读本文。要知道你应该百分之百确定你在做什么。

如果你使用这种方法而不关闭回收,你可能会面临网站在第一次回收发生后不会启动的关键错误。对于那些需要应用程序池回收的网站,我强烈建议将长时间运行的作业移动到不依赖于IIS的单独服务中(如果您使用Azure Web Role -至少在单独的Worker Role实例中)。

我认为,从长远来看,通过适当地将此功能抽象到另一个应用程序(更具体地说是Windows Service),您将获得更积极的结果。在我的公司,我写了一个Daemon,它有很长时间的运行/投票工作,它已经成为我们技术栈的重要组成部分超过4年了。这看起来像是一份工作日志,但它会给你带来回报。

顺便说一句,至于你面临的实际问题;我同意@Vova的观点,因为你的应用程序托管在IIS中,IIS将做很多事情来确保你的应用程序不会使服务器的其他部分瘫痪。其中一些可能包括线程的终止。

这里有几个链接,人们正在谈论你的问题(谷歌一下,你会发现更多):

  • 如何知道谁杀死了我的线程
  • https://channel9.msdn.com/Forums/TechOff/63934-IIS-killing-my-thread
编辑:

我认为我可能应该为那些感兴趣的人详细说明一下这个Daemon服务的体系结构。

基本上它不是太复杂,但非常有效。它由执行任务的"工人"类组成。负载平衡类管理worker的所有实例,并调用它们来完成一小部分工作,因此负载平衡器可以跟踪worker最后做某事的时间,并告诉它们做另一个块,同时大致确保服务器不会承受过多的负载。这可能听起来很棘手,但我保持它相当简单。最酷的部分是每个worker定义了一个处理样式,它可以是:

  • Precalculate:提前计算需要完成的工作量。这允许x (y% ui)更新。这有时是困难的(或不可能的),导致接下来的两种处理类型…
  • Eager:一直做直到没有剩余,不要试图提前评估要做的事情的数量
  • CallOnce:进程函数将被调用一次,然后不检查或计算是否需要做更多的工作而终止

负载均衡器将每个worker的状态存储在MongoDB记录中,因此进程可以容忍故障…话虽如此,尽管每个worker基类确保异常得到处理、记录和电子邮件(尽管这并不意味着内存泄漏之类的事情不会使进程/机器瘫痪)。

要考虑的最后一个方面是如何将工人状态反馈给人类以及如何实现人工干预(暂停、强制运行等)。为此,我公开了非常轻量级的WCF服务,其中只有两个:

  1. 一个获取和控制工作状态的控制服务
  2. 一种命令式消息传递服务,将项目排队以供立即处理。这实际上附加到一个MSMQ,负载均衡器会自动轮询并将其传递给worker。

我们的管理门户使用第一个服务来创建一个页面,该页面自动轮询(使用KnockoutJS)回管理网站,然后到守护进程。

我们发现这是一个非常好的(轻量级和简单的)后端处理套件,可以处理各种各样的任务:Solr记录更新、报告、数据挖掘、文件存储清理等等。