Неожиданное исключение прерывания потока с помощью задач.Зачем? - PullRequest
6 голосов
/ 26 сентября 2011

У меня консольное приложение, работающее в домене приложения.Домен приложения запускается пользовательской службой Windows.Приложение использует родительские задачи для запуска нескольких дочерних задач, которые работают.Может быть много родительских задач с детьми, запущенными в любой момент времени, поскольку таймер ищет новую работу.

Дескриптор всех родительских задач находится в списке задач:

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

Служба Windows может отправлять сигнал остановки запущенному приложению, помещая строковый токен в приложениеслот домена, когда администратор изменяет конфигурационный файл xml (или когда вызывающая служба закрыта).Приложение запускается по таймеру и проверяет сигнал на отключение в слоте, затем пытается изящно завершить свою работу, ожидая завершения всех задач.

Задачи запускаются так:

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 содержит класс, который выполняет эту работу.В функции GAC нет общих объектов.Класс GAC создается в каждой дочерней задаче:

Export export = new Export();

Каждая дочерняя задача вызывает метод в какой-то момент во время выполнения:

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 работает как ожидается для всех дочерних задач.Когда в приложении обнаружен сигнал остановки, оно пытается дождаться завершения всех дочерних задач.Я попробовал несколько способов ожидания завершения дочерних задач под каждым родителем:

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

и

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

Пока код ожидания выполняет поток, возникает исключение:

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

Я не хочу использовать токен отмены, поскольку я хочу, чтобы задачи выполнялись, а не отменялись.

Мне неясно, почему метод класса GAC несчастен, поскольку каждая задача должна иметь уникальныйдескриптор объекта метода.

ОБНОВЛЕНИЕ :

Спасибо за комментарии.Просто для того, чтобы уточнить, что здесь происходит ...

Теоретически, не должно быть никаких оснований для ожидания выполнения дочерних задач:

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

не должноработа, хотя

Task.WaitAll()

, безусловно, является лучшим подходом и помогли прояснить проблему.После дальнейшего тестирования выясняется, что одна из проблем заключалась в том, что, когда приложение домена приложения сообщало вызывающей службе, не выполнялось никаких действий, заполняя слот, считываемый службой:

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

время и место размещенияэто утверждение в коде было неверным в приложении.Будучи новичком в многопоточности, мне потребовалось некоторое время, чтобы понять, что задачи все еще выполняются, когда я выдал SetData «Не экспортировать».И поэтому, когда служба решила, что можно завершить работу и закрыть домен приложения, я полагаю, что это вызвало ThreadAbortException.С тех пор я переместил оператор SetData в более надежное место.

1 Ответ

4 голосов
/ 26 декабря 2011

Чтобы ответить на ваш вопрос: вы получаете прерывание потока, потому что задачи выполняются в «фоновом» потоке.

Фоновые потоки не ожидаются до завершения работы приложения . См. эту ссылку MSDN для дальнейшего объяснения.

Теперь, чтобы попытаться помочь решить реальную проблему, я бы предложил Task.WaitAll() метод, который упоминает Джим, однако вам следует более надежно обрабатывать завершение приложения. Я подозреваю, что пока вы ждете завершения задач перед тем, как завершить работу, вы не препятствуете постановке новых задач в очередь.

Я бы порекомендовал семафор, блокирующий выход, и система, которая запрашивает задачи, увеличивает это при инициализации и уменьшает при удалении.

...