【问题标题】:MVC - System.Threading.ThreadAbortException in a parallel taskMVC - 并行任务中的 System.Threading.ThreadAbortException
【发布时间】:2020-01-18 20:48:13
【问题描述】:

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

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

堆栈跟踪示例:

2015 年 7 月 15 日星期三下午 5:18:36:[报告] 异常(行:456667;Section6):System.Threading.ThreadAbortException:线程被中止。 在 System.Array.Copy(数组 sourceArray,Int32 sourceIndex,数组 destinationArray,Int32 destinationIndex,Int32 长度,布尔可靠) 在 System.Collections.Generic.List`1.set_Capacity(Int32 值) 在 System.Data.Entity.Core.Metadata.Edm.MetadataCollection`1.SetReadOnly() 在 System.Data.Entity.Core.Metadata.Edm.TypeUsage..ctor(EdmType edmType,IEnumerable`1 方面) 在 System.Data.Entity.Core.Common.CommandTrees.DbExpression..ctor(DbExpressionKind 种类,TypeUsage 类型,布尔 forceNullable) 在 System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.PropertyFromMember(DbExpression 实例,EdmMember 属性,字符串 propertyArgumentName) 在 System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler.GenerateEqualityExpression(DbExpressionBinding 目标,EdmProperty 属性,PropagatorResult 值) 在 System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler.BuildPredicate(DbExpressionBinding 目标,PropagatorResult referenceRow,PropagatorResult 当前,TableChangeProcessor 处理器,Boolean & rowMustBeTouched) 在 System.Data.Entity.Core.Mapping.Update.Internal.UpdateCompiler.BuildUpdateCommand(PropagatorResult oldRow,PropagatorResult newRow,TableChangeProcessor 处理器) 在 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,布尔 startLocalTransaction) 在 System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 操作) 在 System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions 选项,布尔型 executeInExistingTransaction) 在 System.Data.Entity.Internal.InternalContext.SaveChanges() 在 MyWebsite.Controllers.AdminPanelController.ApplyUpdate(String filePath, HttpApplicationStateBase context, Int32 saveInterval, Boolean checkRepetitions, String onCollision)

有什么我做错了吗?

【问题讨论】:

  • 您是否在并行查询相同的DbContext
  • 每个任务都会创建自己的上下文。
  • 这是一项长期运行的任务吗?如果是 asp.net mvc 是错误的地方。 IIS 可以随时终止或关闭 asp.net,具体取决于负载等。我猜这就是原因。
  • 我会使用hangfire.ioquartz-scheduler.net 来安排长时间运行的任务。恕我直言,hangfire 更简单且功能强大。
  • 这一定是由于 web 服务在 x 分钟不活动(wrt http 交互)后杀死了 web 应用程序。在 IIS 中,我相信这是 20 分钟。永远不要在 ASP.Net 应用程序中启动长时间运行的任务。这就是制作hangfire 之类的工具的原因(参见之前的评论)。

标签: c# .net asp.net-mvc multithreading entity-framework


【解决方案1】:

我猜这可能与 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 角色 - 至少转移到单独的工作角色实例中)。

【讨论】:

  • 谢谢!正是我想要的。
【解决方案2】:

我认为从长远来看,通过将此功能适当地抽象到另一个应用程序(更具体地说是 Windows 服务)中,您将获得更积极的结果。在我的公司,我编写了一个守护进程,它拥有长期运行/投票的工作人员,4 年来它一直是我们技术堆栈的重要组成部分。 它可能看起来像是一份工作日志,但它会为您带来回报。

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

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

编辑: 我想我应该为那些感兴趣的人详细介绍一下这个守护进程服务的架构。

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

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

负载均衡器将每个工作人员的状态存储在 MongoDB 记录中,因此该进程可以容忍故障......虽然每个工作人员基类都确保异常得到处理、记录和通过电子邮件发送(尽管这并不意味着内存泄漏之类的事情不会导致进程/机器停机)。

要考虑的最后一个方面是如何将工作人员的状态反馈给人类并实现人工干预(暂停、强制运行等)。我通过公开非常轻量级的 WCF 服务来做到这一点,其中也只有 2 个:

  1. 用于获取工作人员状态并对其进行控制的控制服务
  2. 一种命令式消息传递服务,可将项目排队以便立即处理。这实际上附加到负载均衡器轮询并自动传递给工作人员的MSMQ

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-23
    • 1970-01-01
    • 2017-06-28
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    • 1970-01-01
    相关资源
    最近更新 更多