【问题标题】:How to Correctly Cancel a TPL Task with Continuation如何正确取消 TPL 任务并继续
【发布时间】:2012-03-29 18:25:51
【问题描述】:

我有一个长时间运行的操作,我正在使用 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 += delegate { /取消任务/ }) I cancel theTask`,如下所示:

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 =&gt; { ... }); 上方的task.Status 提供的回调中始终是RanToCompletion;应该取消!

在这种情况下,捕获和处理Cancel() 任务的正确方法是什么。我知道在this example from the CodeProjectexamples on MSDN 中显示的简单案例中这是如何完成的,但是在运行延续时我在这种情况下感到困惑。

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

【问题讨论】:

    标签: c# task-parallel-library continuations cancellation request-cancelling


    【解决方案1】:

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

    【讨论】:

    • 虽然理论上可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。
    • 这是我提供的链接。尽管他涵盖了取消任务,但它不在我在这里遇到的更复杂的情况下-因此对我没有用。感谢您的宝贵时间。
    【解决方案2】:

    您在DuplicateSqlProcsFrom 方法中捕获了OperationCanceledException,这会阻止它的Task 看到它并因此将其状态设置为Canceled。因为处理了异常,所以DuplicateSqlProcsFrom 结束时不抛出任何异常,其对应的任务在RanToCompletion 状态下结束。

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

    在您的延续 lambda 中,task.Exception 将是一个 AggregateException,它有一些方便的方法来确定错误的根本原因是什么并进行处理。检查MSDN docs,尤其是InnerExceptions(注意“S”)、GetBaseExceptionFlattenHandle 成员。


    编辑:关于获得TaskStatusFaulted 而不是Canceled

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

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

    Task Cancellation in MSDN

    【讨论】:

    • 谢谢。它确实有帮助。我已经到了从 DuplicateSqlProcsFrom 方法中删除处理的阶段,但仍在尝试在其他地方捕获它。
    • 我按照你的建议做了,但在我的延续方法中,我只得到TaskStatus.Faulted 条件,从来没有TaskStatus.Cancelled。你知道为什么会这样吗?
    • 我已在答案中添加了此行为的解释和解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-29
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 2015-12-07
    相关资源
    最近更新 更多