У меня консольное приложение, работающее в домене приложения.Домен приложения запускается пользовательской службой 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
в более надежное место.