简体   繁体   English

取消时,连续链中的哪个任务正在运行?

[英]Which Task in a continuation chain was running when cancelled?

I have a continuation chain of Task s that may be cancelled, say, after a timeout occurs. 我有一个Task的延续链,例如在发生超时之后,可以取消它。 I want to identify which task was running when the cancellation happened. 我想确定取消发生时正在运行的任务。 Here's what I'm talking about: 这就是我在说的:

CancellationTokenSource cts = new CancellationTokenSource();

Task timeoutTask = Task.Delay(5000).ContinueWith((t) => cts.Cancel());

Task workChain = Task.Factory.StartNew((t) =>
{
    Console.WriteLine("Running task " + Task.CurrentId);
    Thread.Sleep(1000);
}, -1, cts.Token);

Task parent = workChain;
for (int i = 0; i < 10; i++)
{
    parent = parent.ContinueWith((t, o) =>
        {
            Console.WriteLine("Running task " + Task.CurrentId);
            Console.WriteLine("Last Task.AsyncState = " + t.AsyncState);
            Thread.Sleep(1000);
        }, i, cts.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
}
parent.ContinueWith((t) =>
{
    Console.WriteLine("Cancel task " + Task.CurrentId);
    Console.WriteLine("Last running Task.AsyncState = " + t.AsyncState);
}, TaskContinuationOptions.OnlyOnCanceled);

When I run the above, the antecedent passed into the OnlyOnCanceled one isn't the task that was running when things were cancelled. 当我执行上述操作时,传递给OnlyOnCanceled的前提不是取消事情时正在运行的任务。 I know why: the OnlyOnCanceled task is parented by the last task created in the loop, and when the timeout occurs, all tasks that aren't completed are marked as canceled without being started. 我知道为什么: OnlyOnCanceled任务由循环中创建的最后一个任务作为父项,并且当发生超时时,所有未完成的任务都标记为已取消而未启动。

Having each task check the state of the token and store something elsewhere works most of the time, but there's a small chance that the cancellation happens after one task completes before the next task begins. 在大多数情况下,让每个任务检查令牌的状态并在其他地方存储内容是可行的,但是在一个任务完成之后,下一个任务开始之前,取消的可能性很小。 In that case, I don't find out anything about the first canceled task. 在那种情况下,我什么也找不到关于第一个被取消任务的信息。 I could always store something when a task starts and something else if it is canceled, but this starts to feel kludgy pretty quickly. 我总是可以在任务开始时存储一些东西,而在任务被取消时可以存储其他东西,但这很快就会使人感到困惑。

Make each task that might be running record that it started or stopped executing. 使每个可能正在运行的任务记录其开始或停止执行的时间。 The TPL does not record which tasks were running when. TPL不记录哪些任务在何时运行。

What I found works for me, but still doesn't feel as clean as it should: 我发现的内容对我来说很有效,但仍然感觉不如预期:

  • Each task in the main chain doesn't take the CancellationToken , but gets a second continuation (not part of the main chain) that runs OnlyOnFaulted . 主链中的每个任务都不会使用CancellationToken ,而是获得第二个继续执行(不是主链的一部分)的继续执行OnlyOnFaulted
  • Each of those tasks does its work in a child task, then waits on the child task with by calling .Wait(CancellationToken) . 这些任务中的每一个都在子任务中完成其工作,然后通过调用.Wait(CancellationToken)子任务。 When the token is canceled, the Wait call throws an exception, which faults the task that was running. 取消令牌后,“等待”调用将引发异常,从而使正在运行的任务出错。 This allows reporting of the error without waiting on the child task to complete. 这样就可以报告错误,而无需等待子任务完成。
  • Every task in the main chain runs OnlyOnRanToCompletion . 主链中的每个任务都运行OnlyOnRanToCompletion
  • One task at the end of the main chain that runs OnlyOnRanToCompletion reports success of the entire chain (ie no timeout). 运行OnlyOnRanToCompletion的主链末尾的一项任务报告了整个链的成功(即没有超时)。

When the token is canceled, the currently-running task stops waiting for its child task with an OperationCanceledException . 取消令牌后,当前正在运行的任务将停止等待带有OperationCanceledException子任务。 The side branch for that task handles the exception (or any other exception) by special-casing the presence of OperationCanceledException in the antecedent's AggregateException.InnerExceptions . 该任务的侧分支通过特殊处理前一个对象的AggregateException.InnerExceptionsOperationCanceledException的存在来处理异常(或任何其他异常)。 Since the task in the main chain faulted, no other task in the main chain will run. 由于主链中的任务出现故障,因此主链中的其他任务将不会运行。

CancellationTokenSource cts = new CancellationTokenSource(25000);

Task workChain = Task.Factory.StartNew((o) =>
{
    Console.WriteLine("Running task {0} with state {1}", Task.CurrentId, o);
    Thread.Sleep(1000);
}, -1);

Task parent = workChain;
for (int i = 0; i < 10; i++)
{
    parent = parent.ContinueWith((ante, o) =>
        {
            Console.WriteLine("Running task {0} with state {1}", Task.CurrentId, o);
            Task subTask = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(10000);
                Console.WriteLine("Subtask completed");
            });
            subTask.Wait(cts.Token);
        }, i, TaskContinuationOptions.OnlyOnRanToCompletion);
    parent.ContinueWith((ante) =>
        {
            foreach (Exception e in ante.Exception.InnerExceptions)
            {
                if (e is OperationCanceledException)
                {
                    //report timeout
                    Console.WriteLine("Timed out while running task id {0}", ante.Id);
                    return;
                }
            }
            //report other exception
            Console.WriteLine("Something bad happened: {0}", ante.Exception.GetBaseException());
        }, TaskContinuationOptions.OnlyOnFaulted);
}
Task lastTask = parent.ContinueWith((ante) =>
    {
        //report success
        Console.WriteLine("Success");
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

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

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