使用任务的意外线程中止异常.为什么? [英] Unexpected Thread Abort Exception using Tasks. Why?

查看:66
本文介绍了使用任务的意外线程中止异常.为什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个在应用程序域中运行的控制台应用程序.应用程序域由自定义 Windows 服务启动.该应用程序使用父任务来启动几个有效的子任务.当计时器寻找新工作时,可以有许多父任务和子任务在任何给定时间运行.

I have a console application running in an app domain. The app domain is started by a custom windows service. 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>();

当管理员更改 xml 配置文件时(或调用服务关闭时),Windows 服务能够通过将字符串令牌放入应用程序域槽中来向正在运行的应用程序发送停止信号.应用程序在计时器上运行并检查插槽中的关闭信号,然后尝试通过等待所有任务结束来优雅地结束它正在做什么.

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). 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.

任务是这样开始的:

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

一个自定义的 GAC dll 包含一个完成工作的类.GAC 职能中没有共享对象.GAC 类在每个子任务中实例化:

A custom GAC dll holds a class that does the work. There are no shared objects in the GAC function. The GAC class is instantiated in each child task:

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 不在任务之间共享.当应用程序运行时,export.Execute 会按预期对所有子任务执行.当应用程序中检测到停止信号时,它会尝试等待所有子任务结束.我尝试了几种方法来等待每个父级下的子任务结束:

searchResultList is not shared between tasks. While the application runs, export.Execute performs as expected for all child tasks. 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();}

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.

我不清楚为什么 GAC 类方法不愉快,因为每个任务都应该有一个唯一的方法对象句柄.

I am unclear why the GAC class method is unhappy since each task should have a unique handle to the method object.

更新:

感谢您的评论.只是为了进一步澄清这里发生的事情......

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

虽然不行

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:

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

该语句在代码中的时间和位置在应用程序中是错误的.作为多线程的新手,当我将 SetData 发布到不导出"时,我花了一段时间才发现任务仍在运行.因此,当服务认为可以关闭并拆除应用程序域时,我相信这会导致 ThreadAbortException.从那以后,我将 SetData 语句移到了更可靠的位置.

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". And so, when the service thought it was OK to shut down and tore down the app domain, I believe that caused the ThreadAbortException. I've since moved the SetData statement to a more reliable location.

推荐答案

回答您的问题:您收到线程中止,因为任务是在后台"线程上执行的.

To answer your question: you are receiving a thread-abort because tasks are executed on a "background" thread.

在应用程序终止之前不等待后台线程.请参阅此 MSDN 链接 以获取进一步说明.

Background threads are not waited-on before application terminating. See this MSDN link for further explanation.

现在尝试帮助解决您的实际问题,我建议使用 Jim 提到的 Task.WaitAll() 方法,但是您应该更稳健地处理应用程序终止.我怀疑当您在关闭之前等待任务完成时,您并没有阻止新任务排队.

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. 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.

这篇关于使用任务的意外线程中止异常.为什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆