簡體   English   中英

MVC - 並行任務中的 System.Threading.ThreadAbortException

[英]MVC - System.Threading.ThreadAbortException in a parallel task

在我的 MVC 應用程序中,超級管理員可以設置任務隊列,例如更新數據庫。 因此,當管理員向隊列添加更新時,控制器會啟動一個在后台工作的新任務。 但是,當您添加一些任務時,應用程序會拋出System.Threading.ThreadAbortException: Thread was being aborted 此外,堆棧跟蹤表明它發生在代碼的不同行上。

我還應該補充一點,這些任務使用 EF6 實體與 SQL-server 一起工作,並且根據堆棧跟蹤,它發生在對數據庫執行操作之后或期間。 由於更新通常很大,我使用db.Configuration.AutoDetectChangesEnabled = false並每 20k 行手動保存更改,處理和重新創建數據庫。

堆棧跟蹤示例:

2015 年 7 月 15 日星期三下午 5:18:36:[報告] 異常(行:456667;第 6 節):System.Threading.ThreadAbortException:線程被中止。 在 System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean 可靠) at System.Collections.Generic.List`1.set_Capacity(Int32 value) at 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 type, Boolean forceNullable) at System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.PropertyFromMember(DbExpression instance, EdmMember property, String propertyArgumentName) at 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 compiler) 在 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.數據 a.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction) at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation) at System.Data.Entity.Core .Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction) at System.Data.Entity.Internal.InternalContext.SaveChanges() 在 MyWebsite.Controllers.AdminPanelController.ApplyUpdate(String filePath, HttpApplicationStateBase context, Int32 saveInterval, Boolean checkRepetitions, String on Colli )

有什么我可能做錯的嗎?

我想這可能與 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 Application Pool的回收。 仔細閱讀文章的情況下,你不熟悉這個IIS的功能。 請注意,您應該 100% 確定自己在做什么。

如果您在不關閉回收的情況下使用這種方法,您可能會在第一次回收發生后面臨網站無法啟動的嚴重錯誤。 對於需要應用程序池回收的這些網站,我強烈建議將長時間運行的作業移到不依賴於 IIS 的單獨服務中(如果您使用 Azure Web 角色 - 至少移到單獨的工作角色實例中)。

我認為從長遠來看,通過將此功能適當地抽象到另一個應用程序,更具體地說是 Windows 服務,您將獲得更積極的結果。 在我的公司,我編寫了一個守護進程,它擁有長期運行/輪詢的工作人員,並且它已經成為我們技術堆棧的重要組成部分 4 年多了。 這可能看起來像是一份工作日志,但它會回報你的紅利。

順便說一句,至於您面臨的實際問題; 我同意@Vova 的觀點,因為您的應用程序托管在 IIS 中,IIS 會做很多事情來確保您的應用程序不會關閉服務器的其余部分。 其中一些事情可能包括線程的終止。

以下是人們談論您的問題的幾個鏈接(谷歌一下,您會發現更多):

編輯:我認為我應該為那些感興趣的人詳細說明這個守護進程服務的架構。

基本上它不是太復雜,但非常有效。 它由做事的“工人”類組成。 一個負載均衡類管理所有的worker實例並調用它們做一部分工作,因此負載均衡器可以跟蹤一個worker上次做某事的時間並告訴他們做另一塊,同時粗略地確保服務器沒有承受太多的負擔。 這聽起來可能很棘手,但我保持相當簡單。 很酷的部分是每個工人都定義了一種處理風格,它可以是:

  • 預先計算:計算提前完成的工作量。 這允許 x of y% ui 更新。 這有時很困難(或不可能)導致接下來的兩種處理類型......
  • Eager: 一直做到一無所有為止,不要試圖提前評估要做的事情的數量
  • CallOnce:進程函數將被調用一次,然后終止而不檢查或計算是否需要完成更多工作

負載均衡器將每個工作人員的狀態存儲在 MongoDB 記錄中,因此該過程可以容忍失敗......雖然每個工作人員基類都確保處理、記錄和通過電子郵件發送異常(盡管這並不意味着像內存泄漏無法關閉進程/機器)。

要考慮的最后一個方面是如何將工人狀態反饋給人類並實現手動干預(暫停、強制運行等)。 我通過公開非常輕量級的 WCF 服務來做到這一點,其中只有 2 個:

  1. 用於獲取工人狀態並控制它們的控制服務
  2. 一種命令式消息傳遞服務,可將項目排隊以供立即處理。 這實際上附加到負載平衡器輪詢並自動傳遞給工作程序的MSMQ

我們的管理門戶使用第一個服務創建一個頁面,該頁面自動輪詢(使用 KnockoutJS)回管理網站,然后輪詢到守護程序。

我們發現這是一個非常好的(但輕量級和簡單的)后端處理套件,可以處理各種各樣的任務 Solr 記錄更新、報告、數據挖掘、文件存儲清理等等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM