繁体   English   中英

如何正确地继续取消TPL任务

[英]How to Correctly Cancel a TPL Task with Continuation

我有一个长时间运行的操作,我正在使用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;
    }
}

在按钮Click事件中(或使用delegate (buttonCancel.Click + = delegate {/ Cancel the Task /} ) I cancel the Task`,如下所示:

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并打印我的消息,但是在asyncDupSqlProcs.ContinueWith(task => { ... });提供的asyncDupSqlProcs.ContinueWith(task => { ... }); task.Status RanToCompletion 。状态总是RanToCompletion ; 它应该被取消!

在这种情况下,捕获和处理Cancel()任务的正确方法是什么。 我知道这个例子是在CodeProjectMSDN上示例中显示的简单情况下完成的,但在运行延续时我很困惑。

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

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

DuplicateSqlProcsFrom不应该捕获OperationCanceledExceptionAggregateException ,除非它正在等待它自己的子任务。 抛出的任何异常(包括OperationCanceledException )都应该保持不被传播到延续任务。 在你延续的switch语句,你应该检查task.ExceptionFaulted情况和处理Canceled在适当的情况下也是如此。

在你的continuation lambda中, task.Exception将是一个AggregateException ,它有一些方便的方法来确定错误的根本原因,并处理它。 检查MSDN文档特别是InnerExceptions (注意“S”), GetBaseExceptionFlattenHandle成员。


编辑 :获取一个FaultedTaskStatus而不是Canceled

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

DuplicateSqlProcsFrom对令牌调用ThrowIfCancellationRequested时,抛出的OperationCanceledException包含对已取消的令牌的引用。 当Task捕获异常时,它会将该引用与与之关联的CancellationToken进行比较。 如果匹配,则任务转换为Canceled 如果他们不这样做,则编写任务基础结构以假设这是一个无法预料的错误,并且任务转换为Faulted

MSDN中的任务取消

Sacha Barber有很多关于TPL的文章。 试试这一个 ,他描述了简单的任务与延续和取消

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM