Когда нужно утилизировать System.Threading.Task с дочерними задачами?

У меня есть задача, которая запускает несколько дочерних задач. (например, задача A создает B, C, D, E, F). Я также создаю System.Threading.Timer для опроса базы данных каждые 10 секунд, чтобы проверить, был ли запланированный элемент отменен по запросу. Если это так, он устанавливает CancellationTokenSource, чтобы задача могла отказаться. Каждая подзадача, в этом случае B, C, D, E, F, отменит, когда это необходимо (они перемещаются по файлам и перемещают их).

Так как Task реализует IDisposable, я хочу знать, стоит ли снова вызывать Task.WaitAll из блока catch, чтобы дожидаться аннулирования. Пока запрос на отмену будет обработан, подзадачи могут находиться в середине цикла и не могут отменить до тех пор, пока это не завершится

Однако для MSDN:

Всегда вызывайте Dispose перед тем, как вы отпустите свою последнюю ссылку на Задачу. В противном случае ресурсы, которые он использует, не будут освобождены до тех пор, пока сборщик мусора не вызовет метод Finalize объекта Task.

Должен ли я снова вызвать wait на моем массиве задач, чтобы правильно вызвать Dispose() для каждой задачи в массиве?

public class MyCancelObject
{
  CancellationTokenSource Source { get;set;}
  int DatabaseId { get;set;}   
}

private void CheckTaskCancelled(object state)
{
  MyCancelObject sourceToken = (MyCancelObject)state;

  if (!sourceToken.CancelToken.IsCancellationRequested)
  {
    //Check database to see if cancelled -- if so, set to cancelled
    sourceToken.CancelToken.Cancel();
  }
}

private void SomeFunc()
{
  Task.StartNew( () =>
  {
    MyCancelObject myCancelObject = new MyCancelObject(
      databaseId,
      new CancellationTokenSource());
    System.Threading.Timer cancelTimer = new Timer(
      new TimerCallback(CheckIfTaskCancelled),
      myCancelObject,
      10000,
      10000);        
    Task[] someTasks = new Task[someNumberOfTasks];

    for (int i = 0; i < someNumberOfTasks; i++)
      someTasks[i] = Task.Factory.StartNew(
        () =>
        {
          DoSomeWork(someObject, myCancelObject.CancelToken.Token);
        },
        TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning,
        myCancelObject.CancelToken.Token);

    try
    {
      Task.WaitAll(someTasks, cts);
    }
    catch (AggregateException)
    {
      //Do stuff to handle
    }
    catch (OperationCanceledException)
    {
      //Should I call Task.WaitAll(someTasks) again??
      //I want to be able to dispose.
    }
  }
}

Ответ 1

Мне кажется, что я понял это, но любой, кто хотел бы добавить что-нибудь полезное, более чем приветствуется.

Я просто вызвал Task.WaitAll() снова из блока catch, чтобы дождаться завершения остальных задач. После того, как они все закончили, у меня наконец-то блок очистки всех задач в массиве.

try
{
Task.WaitAll(someTaskArray, cancelToken)
}
catch (OperationCanceledException)
{
Task.WaitAll(someTaskArray);
}
finally
{
for (int i = 0; i < someTaskArray.Length; i++)
someTaskArray[i].Dispose();
}