Использование CancellationToken для Task.Run

У меня есть код, который использует 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() извне объекта, чтобы было поймано отмененное исключение?


person user3736648    schedule 04.04.2016    source источник


Ответы (1)


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

Проблема в том, что ваш пользовательский код тратит большую часть времени на ожидание 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");
    }
}

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

person Kirill Shlenskiy    schedule 04.04.2016
comment
Не обращая внимания на SpeechSynthesizer, правильно ли я говорю, что когда Task.Run инициируется, нет никакого способа обнаружить запрос на отмену, кроме как в начале функции? - person user3736648; 04.04.2016
comment
@user3736648 user3736648, кажется, у вас правильная идея, но формулировка нуждается в доработке (см. Первый абзац). Проще говоря, Task.Run проверяет CancellationToken один раз, в самом начале, перед выполнением вашего делегата в пуле потоков. Когда вызов делегата уже запущен, Task.Run ничего не может сделать. - person Kirill Shlenskiy; 04.04.2016
comment
Спасибо. Помимо Task.Run, есть ли правильный способ создать поток, который можно отменить в любое время? - person user3736648; 04.04.2016
comment
@ user3736648, это технически невозможно (поскольку будет сложно рассуждать о состоянии систем, поддерживающих отмену). Что вам нужно, так это совместная отмена (т. е. API, которые изначально поддерживают отмену, например вызов SpeakAsyncCancelAll, о котором я упоминал ранее). MSDN: msdn.microsoft.com/en-us /библиотека/dd997364(v=vs.110).aspx - person Kirill Shlenskiy; 04.04.2016
comment
Вы можете, конечно, отказаться от своей асинхронной операции (в данном случае от воспроизведения речи), но это именно то, на что это похоже: синтезатор будет продолжать играть, даже если Task будет отменен: blogs.msdn .com/b/pfxteam/archive/2012/10/05/ - person Kirill Shlenskiy; 04.04.2016