简体   繁体   English

使用任务发生意外的线程中止异常。 为什么?

[英]Unexpected Thread Abort Exception using Tasks. Why?

I have a console application running in an app domain. 我有一个在应用程序域中运行的控制台应用程序。 The app domain is started by a custom windows service. 应用程序域由自定义Windows服务启动。 The application uses parent tasks to start several child tasks that do work. 该应用程序使用父任务来启动多个有效的子任务。 There can be many parent tasks with children running at any given time as the timer looks for new work. 当计时器查找新工作时,可以有许多子任务在任何给定时间运行子项。

The handle to all parent tasks is in a List of tasks: 所有父任务的句柄位于任务列表中:

 static List<Task> _Tasks = new List<Task>();

The windows service is able to send a stop signal to the running application by putting a string token in an app domain slot when an admin changes an xml config file (or when the calling service is shut down). 当管理员更改xml配置文件时(或当呼叫服务关闭时),Windows服务可以通过在应用程序域槽中放置字符串令牌来向正在运行的应用程序发送停止信号。 The application is running on a timer and checks for a signal to shut down in the slot, then attempts to gracefully conclude what it is doing by waiting for all tasks to end. 应用程序在计时器上运行并检查要在插槽中关闭的信号,然后尝试通过等待所有任务结束来优雅地结束它正在执行的操作。

Tasks are started like so: 任务开始如下:

Task parent = Task.Factory.StartNew(() =>
            {
                foreach (var invoiceList in exportBucket)
                {
                    KeyValuePair<string, List<InvoiceInfo>> invoices = new KeyValuePair<string, List<InvoiceInfo>>();
                    invoices = invoiceList;
                    string taskName = invoices.Key; //file name of input file
                    Task<bool> task = Task.Factory.StartNew<bool>(state => ExportDriver(invoices),
                        taskName, TaskCreationOptions.AttachedToParent);
                }
            });
            _Tasks.Add(parent);

A custom GAC dll holds a class that does the work. 自定义GAC dll包含一个完成工作的类。 There are no shared objects in the GAC function. GAC功能中没有共享对象。 The GAC class is instantiated in each child task: GAC类在每个子任务中实例化:

Export export = new Export();

Each child task calls a method at some point during execution: 每个子任务在执行期间的某个时刻调用方法:

foreach (var searchResultList in SearchResults)
{
      foreach (var item in searchResultList)
      {
          if (item.Documents.Count > 0)
          {
              //TODO: this is where we get thread issue if telling service to stop
              var exported = export.Execute(searchResultList);
              totalDocuments += exported.ExportedDocuments.Count();
          }
      }
 }

searchResultList is not shared between tasks. searchResultList不在任务之间共享。 While the application runs, export.Execute performs as expected for all child tasks. 应用程序运行时,export.Execute将按预期执行所有子任务。 When the stop signal is detected in the application, it attempts to wait for all child tasks to end. 当在应用程序中检测到停止信号时,它会尝试等待所有子任务结束。 I've tried a couple ways to wait for the child tasks under each parent to end: 我已经尝试了几种方法来等待每个父项下的子任务结束:

foreach (var task in _Tasks){task.Wait();}

and

while (_Tasks.Count(t => t.IsCompleted) != _Tasks.Count){}

While the wait code executes a threading exception occurs: 等待代码执行时会发生线程异常:

Error in Export.Execute() method: System.Threading.ThreadAbortException: Thead was being aborted at WFT.CommonExport.Export.Execute(ICollection '1 searchResults)

I do not wish to use a cancellation token as I want the tasks to complete, not cancel. 我不希望使用取消令牌,因为我希望任务完成,而不是取消。

I am unclear why the GAC class method is unhappy since each task should have a unique handle to the method object. 我不清楚为什么GAC类方法不满意,因为每个任务都应该有一个方法对象的唯一句柄。

UPDATE : 更新

Thanks for the comments. 感谢您的评论。 Just to add further clarification to what was going on here... 只是为了进一步澄清这里发生的事情......

In theory, there shouldn't be any reason the approach of waiting on child tasks: 从理论上讲,等待儿童任务的方法应该没有任何理由:

while (_Tasks.Count(t => t.IsCompleted) != _Tasks.Count){}

shouldn't work, though 但是不应该工作

Task.WaitAll()

is certainly a better approach, and helped flesh out the problem. 肯定是一个更好的方法,并帮助充实问题。 After further testing it turns out that one of the issues was that when the app domain application told the calling service no work was being done by populating a slot read by the service: 经过进一步测试后发现其中一个问题是,当app域应用程序告诉调用服务时,没有通过填充服务读取的插槽来完成工作:

AppDomain.CurrentDomain.SetData("Status", "Not Exporting");

the timing and placement of that statement in code was wrong in the application. 该语句在代码中的时间和位置在应用程序中是错误的。 Being new to multithreading, it took me a while to figure out tasks were still running when I issued SetData to "Not Exporting". 作为多线程的新手,当我将SetData发布为“Not Exporting”时,我花了一段时间才弄清楚任务仍在运行。 And so, when the service thought it was OK to shut down and tore down the app domain, I believe that caused the ThreadAbortException . 因此,当服务认为可以关闭并删除应用程序域时,我认为这会导致ThreadAbortException I've since moved the SetData statement to a more reliable location. 我已经将SetData语句移动到更可靠的位置。

To answer your question: you are receiving a thread-abort because tasks are executed on a "background" thread. 回答你的问题:你正在接收线程中止,因为任务是在“后台”线程上执行的。

Background threads are not waited-on before application terminating . 在应用程序终止之前,不会等待后台线程 See this MSDN link for further explanation. 有关详细说明,请参阅此MSDN链接

Now to try to help solve your actual problem, I would suggest the Task.WaitAll() method Jim mentions, however you should handle application termination more robustly. 现在尝试帮助解决您的实际问题,我建议Jim提到的Task.WaitAll()方法,但是您应该更强大地处理应用程序终止。 I suspect that while you wait for tasks to complete before shutting down, you don't prevent new tasks from being enqueued. 我怀疑在关闭之前等待任务完成时,您不会阻止新任务入队。

I would recommend an exit-blocking semaphore and the system that enques tasks increment this on initialization, and decrement on dispose. 我建议使用一个出口阻塞信号量和enques任务的系统在初始化时递增它,并在dispose上递减。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM