2013-10-11 7 views
5

У меня есть многопоточное приложение, где мне нужно отменить каждую задачу через определенное время, даже если во время отмены они используют неуправляемые ресурсы. Теперь я использую следующий код (например, консольное приложение). В реальном приложении задержка может возникать в неуправляемом ресурсе.Отменить задание по времени

static void Main() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      Task.Factory.StartNew(Do, TaskCreationOptions.LongRunning); 
     } 

     Console.ReadLine(); 
    } 

    private static void Do() 
    { 
     new Timer(Thread.CurrentThread.Abort, null, 1000, -1); 

     try 
     { 
      Console.WriteLine("Start " + Task.CurrentId); 
      Thread.Sleep(2000); 
      Console.WriteLine("End " + Task.CurrentId); 
     } 
     catch (Exception) 
     { 
      Console.WriteLine("Thread Aborted " + Task.CurrentId); 
     } 
    } 

Получить результат:

enter image description here

Но я не уверен, правильно ли это для реального приложения с точки зрения безопасности. Я также использовал CancellationToken в различных вариантах, но это не дает мне правильный результат, потому что, когда я использую CancellAfter() или .Delay() с временным интервалом и отменить задание после того, как ceratain времени я получил следующие результаты:

static void Main() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      var clt = new CancellationTokenSource(); 

      Task task = new Task(() => 
      { 
       Task.Delay(2000).ContinueWith(_ => 
       { 
        clt.Cancel(); 

       }, clt.Token); 

       Do(clt.Token); 

      }, clt.Token); 

      task.Start(); 
     } 

     Console.ReadLine(); 
    } 

    private static void Do(CancellationToken cltToken) 
    { 
     Console.WriteLine("Start " + Task.CurrentId); 

     Thread.Sleep(2500); 

     if (!cltToken.IsCancellationRequested) 
     { 
      Console.WriteLine("End " + Task.CurrentId); 
     } 
     else 
     { 
      Console.WriteLine("Cancelled "+ Task.CurrentId); 
     } 
    } 

enter image description here

В этой ситуации все задачи должны быть отменены, так как Thread.Sleep()> времени, выделенного для выполнения каждой задачи. Но мы можем видеть, что некоторое время для выполнения.

Я также использую следующую конструкцию и дают тот же результат:

 static void Main() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      var clt = new CancellationTokenSource(); 
      clt.CancelAfter(2000); 

      Task.Factory.StartNew(Do, clt.Token); 

     } 

     Console.ReadLine(); 
    } 

    private static void Do(object obj) 
    { 
     var cltToken = (CancellationToken) obj; 

     Console.WriteLine("Start " + Task.CurrentId); 

     Thread.Sleep(2500); 

     if (!cltToken.IsCancellationRequested) 
     { 
      Console.WriteLine("End " + Task.CurrentId); 
     } 
     else 
     { 
      Console.WriteLine("Cancelled "+ Task.CurrentId); 
     } 
    } 

enter image description here

Я также использую Parallel и инициализировать отмены Токен Внутри метода Do(), и использовать таймер для cancell маркер после промежутка времени, но все дают тот же результат.

ТАК, Почему это происходит и каков правильный способ отменить задачу через определенное время ???

ответ

5

Вы можете получить тот же результат, что и исходная версия «прервать», используя те же тайминги. Например, этот код:

static void Main() 
{ 
    var clt = new CancellationTokenSource(); 
    clt.CancelAfter(1000); 
    for (int i = 0; i < 10; i++) 
    { 
     Task.Run(() => Do(clt.Token)); 
    } 
    Console.ReadLine(); 
} 

private static void Do(CancellationToken cltToken) 
{ 
    Console.WriteLine("Start " + Task.CurrentId); 
    Thread.Sleep(2000); 

    if (!cltToken.IsCancellationRequested) 
    { 
     Console.WriteLine("End " + Task.CurrentId); 
    } 
    else 
    { 
     Console.WriteLine("Cancelled "+ Task.CurrentId); 
    } 
} 

будет производить что-то simliar к:

Start 111 
Start 112 
Start 113 
Start 114 
Start 115 
Start 116 
Start 117 
Start 118 
Start 119 
Start 120 
Cancelled 111 
Cancelled 112 
Cancelled 118 
Cancelled 116 
Cancelled 114 
Cancelled 113 
Cancelled 117 
Cancelled 115 
Cancelled 119 
Cancelled 120 

Использование CancellationTokenSource является лучшим вариантом, чем отбрасывание темы. Thread.Abort - плохая идея, так как она прерывает поток, не обеспечивая надлежащих механизмов очистки. Использование маркера позволяет вам сотрудничать обрабатывать отмену в чистом виде.

Что касается того, почему ваши другие варианты не функционировали должным образом - тайминги, которые вы использовали, были слишком близки друг к другу. Это особенно актуально при работе под отладчиком, так как это предотвратит одновременное срабатывание таймингов (то есть: CancelAfter, а также Thread.Sleep). Если вы запустите сборку релиза вне хост-процесса Visual Studio, вы, скорее всего, обнаружите, что они работают гораздо надежнее.

1

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

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

  • Очевидно, что нет способа опросить CancellationToken, пока поток выполняет неуправляемый код. Таким образом, нет возможности инициировать изящное завершение самостоятельно.
  • Помимо того факта, что вы не должны прерывать поток, в любом случае вызов Thread.Abort не будет вводить сигнал прерывания в цель до тех пор, пока он не присоединится к управляемому царству. Другими словами, прерывания не будут прерывать потоки, выполняющие неуправляемый код. Это было сделано намеренно, чтобы сделать прерывания более безопасными.

Единственный способ сделать это надежно - запустить неуправляемый ресурс вне процесса. Это означает, что вам нужно будет раскрутить новый процесс для выполнения неуправляемого кода, а затем использовать WCF (или другой протокол связи) для отправки сообщений/данных взад и вперед. Если неуправляемый ресурс не отвечает своевременно, вы можете убить процесс. Это безопасно, потому что убийство другого процесса не искажает состояние текущего процесса.

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