2016-06-20 8 views
0

Я пытаюсь включить опцию Kill для случая, который выполняется асинхронно с вызывающего потока, как Process. Мой пример выглядит как ответ @ svick here. Я пытаюсь выполнить рекомендацию @svick в этом post. Но когда я нажимаю на Kill в пользовательском интерфейсе, он, кажется, ничего не делает (т. Е. Процесс просто выполняется до завершения, как обычно).Остановка процесса с использованием CancellationToken

В соответствии с ответом TimCopenhaver, это ожидается. Но если я прокомментировал это, он все равно ничего не сделает, на этот раз, потому что объект CancellationTokenSource является нулевым, что является неожиданным, так как я создаю его в методе Dispatch класса CaseDispatcher, прежде чем пытаться его убить. Вот мой фрагмент кода:

UI класс:

private void OnKillCase(object sender, EventArgs args) 
{ 
    foreach (var case in Cases) 
    { 
     Args caseArgs = CaseAPI.GetCaseArguments(case); 
     CaseDispatcher dispatcher = CaseAPI.GetCaseDispatcher(case); 
     dispatcher.Kill(); 
     CaseAPI.Dispose(caseArgs); 
    } 
} 

CaseDispatcher класс:

private Task<bool> task; 
    private CancellationTokenSource cts; 
    public override bool IsRunning 
    { 
     get { return task != null && task.Status == TaskStatus.Running; } 
    } 
    public override void Kill() 
    { 
     //if (!IsRunning) 
     //{ 
     // return; 
     //} 
     if (cts != null) 
     { 
      cts.Cancel(); 
     } 
    } 
    public override async Task<bool> Dispatch() 
    { 
     cts = new CancellationTokenSource(); 
     task = CaseAPI.Dispatch(Arguments, cts.Token); 
     return await task; 
    } 

CaseAPI класс:

public static async Task<bool> Dispatch(CaseArgs args, CancellationToken ctoken) 
{ 
    bool ok = true; 
    BatchEngine engine = new BatchEngine() 
     { 
      Spec = somespec, 
      CaseName = args.CaseName, 
      CaseDirectory = args.CaseDirectory 
     }; 
    ok &= await engine.ExecuteAsync(ctoken); 
    return ok; 
} 

BatchEngine класс (вот где я призываю CancellationTokenRegister метод, но не знаю точно, где поместить его, предполагая, что это имеет значение):

public virtual Task<bool> ExecuteAsync(CancellationToken ctoken) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    string exe = Spec.GetExecutablePath(); 
    string args = string.Format("--input={0} {1}", Input, ConfigFile); 

    try 
    { 
     var process = new Process 
     { 
      EnableRaisingEvents = true, 
      StartInfo = 
      { 
       UseShellExecute = false, 
       FileName = exe, 
       Arguments = args, 
       CreateNoWindow = true, 
       RedirectStandardOutput = true, 
       RedirectStandardError = true, 
       WorkingDirectory = CaseDirectory 
      } 
     }; 
     ctoken.Register(() => 
      { 
       process.Kill(); 
       process.Dispose(); 
       tcs.SetResult(false); 
      }); 
     process.Exited += (sender, arguments) => 
     { 
      if (process.ExitCode != 0) 
      { 
       string errorMessage = process.StandardError.ReadToEnd(); 
       tcs.SetResult(false); 
       tcs.SetException(new InvalidOperationException("The batch process did not exit correctly. Error message: " + errorMessage)); 
      } 
      else 
      { 
       File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd()); 
       tcs.SetResult(true); 
      } 
      process.Dispose(); 
     }; 
     process.Start(); 
    } 
    catch (Exception e) 
    { 
     Logger.InfoOutputWindow(e.Message); 
     tcs.SetResult(false); 
     return tcs.Task; 
    } 
    return tcs.Task; 
} 

Благодарим Вас за интерес и оценить какие-либо идеи по этому поводу.

+0

'ctoken.Register' возвращает объект, который должен быть удален в обработчике событий« Exited ». –

+2

Это должно в основном работать (помимо нескольких условий гонки, которые, вероятно, не вызывают немедленной проблемы). Поместите точки останова на cts.Cancel и process.Kill, чтобы увидеть, где это происходит не так. Bisect. – usr

+0

Можете ли вы показать код своего свойства IsRunning для CaseDispatcher? Где это устанавливается? –

ответ

1

Я считаю, что свойство IsRunning является проблемой. Поскольку TaskCompletionSource действительно не знает, что вы начали внешний процесс, он застрял в состоянии WaitingForActivation. Вот упрощенный пример, чтобы продемонстрировать:

var tsc = new TaskCompletionSource<int>(); 

Task.Factory.StartNew(() => 
{ 
    Thread.Sleep(10000); 
    tsc.SetResult(10); 
}); 

var tmp = tsc.Task; 

TaskStatus status = tmp.Status; 
while (status != TaskStatus.RanToCompletion) 
{ 
    status = tmp.Status; 
    Thread.Sleep(1000); 
    Console.WriteLine(status); 
} 

Обратите внимание, что будет продолжать говорить WaitingForActivation до тех пор, пока не переходит в RanToCompletion. См. this answer для более подробного обсуждения этого вопроса. Короче говоря, если задача создана TaskCompletionSource, она никогда не войдет в текущее состояние. Вам придется самостоятельно управлять собственностью IsRunning.

+0

Из того, что я узнал до сих пор, для пакетных процессов, которые используют этот процесс, мы должны использовать TaskCompletionSource для запуска асинхронно из вызывающего потока, правильно? Это означает, что эта проблема не существует, если мы хотим убить такие процессы. Кроме того, когда я отлаживаю это, я замечаю, что cts также является нулевым в методе Kill, и поэтому снова он не входит, даже если экземпляр cts был создан, это было неожиданно для меня. Есть идеи по этому поводу? –

+1

Вы по-прежнему можете отменить, но нет способа обходного пути для проверки состояния выполняемой задачи.Для второй проблемы, когда cts имеет значение null, я подозреваю ошибку в GetCaseDispatcher. Если экземпляр CaseDispatcher не кэширован правильно, вы можете получить новый экземпляр, который не имеет установленных свойств. –

+0

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