2016-04-04 4 views
0

У меня есть код, который использует Task.Run с маркером отмены.Использование CancellationToken для Task.Run

Вот мой код:

public class TaskObject 
{ 
    CancellationTokenSource _source = new CancellationTokenSource(); 
    public async Task TaskAction() 
    { 
     var task = Task.Run(async delegate 
     { 
      await TaskRun(); 
     }, _source.Token); 

     //TaskCancel(); 

     try 
     { 
      task.Wait(); 
     } 
     catch (Exception ex) 
     { 

     } 
    } 
    public async Task TaskRun() 
    { 
     if (_source.IsCancellationRequested) 
     { 
      _source.Token.ThrowIfCancellationRequested(); 
     } 

     SpeechSynthesizer _speechSynthesizer = new SpeechSynthesizer(); 
     _speechSynthesizer.SpeakAsync("This is a test prompt"); 
    } 
    public void TaskCancel() 
    { 
     _source.Cancel(); 
    } 
} 

Если я называю TaskCancel() в TaskAction(), задача отменена исключение ловится.

Если я вызываю TaskCancel() вне объекта, отмененное исключение не будет обнаружено.

Вот код, чтобы показать, где отменено исключение не перехвачено:

taskObject = new TaskObject(); 
await taskObject.TaskAction(); 
taskObject.TaskCancel(); 

Как я могу назвать TaskCancel() снаружи объекта, так что отмененное исключение перехватывается?

ответ

4

Отмена должна быть совместной. Task.Run не знает, какие побочные эффекты может иметь назначенный делегат, поэтому единственная логически безопасная точка, в которой Task.Run может проверить ваш CancellationToken и выбросить OCE (не выходя из приложения в потенциально несогласованном состоянии), - до, начинается операция async - после этого времени код пользователя проверяется на токен.

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

SpeechSynthesizer, тем не менее, есть SpeakAsyncCancelAll метод, который вы можете подключить его в как так:

public async Task TaskRun(CancellationToken ct) 
{ 
    ct.ThrowIfCancellationRequested(); 

    SpeechSynthesizer _speechSynthesizer = new SpeechSynthesizer(); 

    using (ct.Register(() => _speechSynthesizer.SpeakAsyncCancelAll())) { 
     await _speechSynthesizer.SpeakAsync("This is a test prompt"); 
    } 
} 

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

+0

Не обращайте внимания на SpeechSynthesizer, правильно ли я говорю, что при запуске Task.Run невозможно определить запрос отмены, если только в начале функции? – user3736648

+0

@ user3736648, у вас появляется правильная идея, но формулировка нуждается в полировке (см. Первый абзац). Проще говоря, 'Task.Run' проверяет' CancellationToken' один раз, в самом начале, перед выполнением вашего делегата в пуле потоков. Когда вызов делегата выполняется, ничего не может сделать «Task.Run». –

+0

Спасибо. Помимо Task.Run, существует ли правильный способ создания потока, который можно отменить в любое время? – user3736648