Как правильно отменить задачу TPL с продолжением - PullRequest
1 голос
/ 13 марта 2012

У меня есть длительная операция, которую я помещаю в фоновый поток, используя TPL.То, что у меня в данный момент работает, но я не понимаю, где мне следует обрабатывать мой AggregateException во время запроса на отмену.

В случае нажатия кнопки я запускаю свой процесс:

private void button1_Click(object sender, EventArgs e)
{
    Utils.ShowWaitCursor();
    buttonCancel.Enabled = buttonCancel.Visible = true;
    try
    {
        // Thread cancellation.
        cancelSource = new CancellationTokenSource();
        token = cancelSource.Token;

        // Get the database names.
        string strDbA = textBox1.Text;
        string strDbB = textBox2.Text;

        // Start duplication on seperate thread.
        asyncDupSqlProcs =
            new Task<bool>(state =>
                UtilsDB.DuplicateSqlProcsFrom(token, mainForm.mainConnection, strDbA, strDbB),
                "Duplicating SQL Proceedures");
        asyncDupSqlProcs.Start();

        //TaskScheduler uiThread = TaskScheduler.FromCurrentSynchronizationContext();
        asyncDupSqlProcs.ContinueWith(task =>
            {
                switch (task.Status)
                {
                    // Handle any exceptions to prevent UnobservedTaskException.             
                    case TaskStatus.Faulted:
                        Utils.ShowDefaultCursor();
                        break;
                    case TaskStatus.RanToCompletion:
                        if (asyncDupSqlProcs.Result)
                        {
                            Utils.ShowDefaultCursor();
                            Utils.InfoMsg(String.Format(
                                "SQL stored procedures and functions successfully copied from '{0}' to '{1}'.",
                                strDbA, strDbB));
                        }
                        break;
                    case TaskStatus.Canceled:
                        Utils.ShowDefaultCursor();
                        Utils.InfoMsg("Copy cancelled at users request.");
                        break;
                    default:
                        Utils.ShowDefaultCursor();
                        break;
                }
            }, TaskScheduler.FromCurrentSynchronizationContext()); // Or uiThread.

        return;
    }
    catch (Exception)
    {
        // Do stuff...
    }
}

Вметод DuplicateSqlProcsFrom(CancellationToken _token, SqlConnection masterConn, string _strDatabaseA, string _strDatabaseB, bool _bCopyStoredProcs = true, bool _bCopyFuncs = true) У меня есть

DuplicateSqlProcsFrom(CancellationToken _token, SqlConnection masterConn, string _strDatabaseA, string _strDatabaseB, bool _bCopyStoredProcs = true, bool _bCopyFuncs = true)
{ 
    try
    {
        for (int i = 0; i < someSmallInt; i++)
        {
            for (int j = 0; j < someBigInt; j++)
            {
                // Some cool stuff...
            }

            if (_token.IsCancellationRequested)
                _token.ThrowIfCancellationRequested();
        }
    }
    catch (AggregateException aggEx)
    {
        if (aggEx.InnerException is OperationCanceledException)
            Utils.InfoMsg("Copy operation cancelled at users request.");
        return false;
    }
    catch (OperationCanceledException)
    {
        Utils.InfoMsg("Copy operation cancelled at users request.");
        return false;
    }
}

В событии нажатия кнопки (или с использованием delegate (buttonCancel.Click + = делегат {/ * Отмена задачи /} ) I cancel the Задача` выглядит следующим образом:

private void buttonCancel_Click(object sender, EventArgs e)
{
    try
    {
        cancelSource.Cancel();
        asyncDupSqlProcs.Wait();
    }
    catch (AggregateException aggEx)
    {
        if (aggEx.InnerException is OperationCanceledException)
            Utils.InfoMsg("Copy cancelled at users request.");
    }
}

Это ловит штраф OperationCanceledException в методе DuplicateSqlProcsFrom и печатает мое сообщение, но при обратном вызове, предоставленном asyncDupSqlProcs.ContinueWith(task => { ... }); над task.Status,всегда RanToCompletion; его следует отменить!

Как правильно в этом случае захватить и справиться с задачей Cancel(). Я знаю, как это делается в простых случаях, показанных в этот пример из CodeProject и из примеров на MSDN , но я запутался в этом случае при запуске продолжения.

Как мне захватить задачу отмены в этом случае и какчтобы убедиться, что task.Status обработан правильно?

Ответы [ 2 ]

3 голосов
/ 13 марта 2012

Вы ловите OperationCanceledException в своем методе DuplicateSqlProcsFrom, который предотвращает его Task, и соответственно устанавливает его статус на Canceled. Поскольку исключение обрабатывается, DuplicateSqlProcsFrom завершается без каких-либо исключений, а соответствующая задача завершается в состоянии RanToCompletion.

DuplicateSqlProcsFrom не должен перехватывать либо OperationCanceledException, либо AggregateException, если только он не ожидает собственных подзадач. Любые сгенерированные исключения (включая OperationCanceledException) должны быть оставлены необработанными для передачи в задачу продолжения. В вашем продолжении switch вы должны проверить task.Exception в случае Faulted и обработать Canceled в соответствующем случае.

В вашем продолжении лямбда, task.Exception будет AggregateException, в котором есть несколько удобных методов для определения первопричины ошибки и ее обработки. Проверьте MSDN документы , в частности, для InnerExceptions (обратите внимание на "S"), GetBaseException, Flatten и Handle членов.


РЕДАКТИРОВАТЬ : при получении TaskStatus из Faulted вместо Canceled.

В строке, где вы создаете задачу asyncDupSqlProcs, используйте конструктор Task, который принимает как ваш DuplicateSqlProcsFrom делегат, так и CancellationToken. Это связывает ваш токен с заданием.

Когда вы вызываете ThrowIfCancellationRequested на токене в DuplicateSqlProcsFrom, брошенный OperationCanceledException содержит ссылку на токен, который был отменен. Когда Задача ловит исключение, она сравнивает эту ссылку с CancellationToken, связанным с ним. Если они совпадают, то задача переходит на Canceled. Если они этого не делают, инфраструктура Task была написана так, чтобы предполагать, что это непредвиденная ошибка, и вместо этого задача переходит на Faulted.

Отмена задачи в MSDN

0 голосов
/ 13 марта 2012

У Саши Барбера есть отличная серия статей о TPL. Попробуйте это one , он описывает простую задачу с продолжением и отменой

...