如何正确取消连续的TPL任务

本文关键字:TPL 任务 连续 何正确 取消 | 更新日期: 2023-09-27 18:27:32

我有一个长时间运行的操作,我正在使用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(button.Cancel.Click+=delegate{/取消任务/}) 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.");
    }
}

这在方法DuplicateSqlProcsFrom中捕获了OperationCanceledException fine并打印我的消息,但在task.Status之上的asyncDupSqlProcs.ContinueWith(task => { ... });提供的回调中始终是RanToCompletion;应该取消!

在这种情况下,捕获和处理Cancel()任务的正确方法是什么。我从CodeProject和MSDN上的例子中知道在这个例子中显示的简单情况下是如何做到这一点的,但在运行continuation时,我对这个例子感到困惑。

在这种情况下,我如何捕获取消任务,以及如何确保正确处理task.Status

如何正确取消连续的TPL任务

您正在DuplicateSqlProcsFrom方法中捕获OperationCanceledException,这将阻止其Task看到它,并相应地将其状态设置为Canceled。由于处理了异常,DuplicateSqlProcsFrom在不引发任何异常的情况下完成,其相应的任务在RanToCompletion状态下完成。

DuplicateSqlProcsFrom不应该捕获OperationCanceledExceptionAggregateException,除非它正在等待自己的子任务。任何抛出的异常(包括OperationCanceledException)都应保持未捕获状态,以便传播到延续任务。在continuation的switch语句中,您应该在Faulted的情况下检查task.Exception,并在适当的情况下处理Canceled

在延续lambda中,task.Exception将是一个AggregateException,它有一些方便的方法来确定错误的根本原因并进行处理。请查看MSDN文档,特别是InnerExceptions(注意"S")、GetBaseExceptionFlattenHandle成员。


EDIT:关于获取FaultedTaskStatus而不是Canceled

在构造asyncDupSqlProcs任务的行中,使用Task构造函数,该构造函数接受DuplicateSqlProcsFrom委托和CancellationToken。将您的令牌与任务关联起来。

当您对DuplicateSqlProcsFrom中的令牌调用ThrowIfCancellationRequested时,抛出的OperationCanceledException包含对已取消的令牌的引用。当Task捕捉到异常时,它会将该引用与关联的CancellationToken进行比较。如果它们匹配,则任务转换为Canceled。如果他们没有这样做,那么任务基础结构已经被编写为假设这是一个不可预见的错误,而任务将转换到Faulted

MSDN 中的任务取消

Sacha Barber有一系列关于TPL的文章。试试这个,他描述了一个简单的任务,包括继续和取消