Task.ContinueWith() 正在执行,但任务状态仍为“正在运行”

Rev*_*1.0 16 c# asynchronous task

考虑以下代码:

MyTask = LongRunningMethod(progressReporter, CancelSource.Token)
    .ContinueWith(e => 
        { Log.Info("OnlyOnCanceled"); }, 
        default, 
        TaskContinuationOptions.OnlyOnCanceled,
        TaskScheduler.FromCurrentSynchronizationContext())
    .ContinueWith(e => 
        { Log.Info("OnlyOnFaulted"); }, 
        default, 
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.FromCurrentSynchronizationContext())
    .ContinueWith(e => 
        { Log.Info("OnlyOnRanToCompletion"); }, 
        default,
        TaskContinuationOptions.OnlyOnRanToCompletion, 
        TaskScheduler.FromCurrentSynchronizationContext())
    .ContinueWith(e => 
        { Log.Info("None"); }, 
        default, 
        TaskContinuationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());
Run Code Online (Sandbox Code Playgroud)
  • 当我使用提供的 CancelSource 取消任务时,输出为:
    OnlyOnCanceled
    None
    正如预期的那样。

  • 当 LongRunningMethod 抛出异常时,输出为:
    OnlyOnFaulted
    None
    正如预期的那样。

  • LongRunningMethod完成输出是:
    None
    所以ContinueWithwithTaskContinuationOptions.OnlyOnRanToCompletion没有像我期望的那样执行。

我检查MyTask.Status了最后一个ContinueWith分支,它仍然是Running. 因此,考虑到这一点,我希望跳过 OnlyOnRanToCompletion。问题是,为什么是Statusstill Running?查看调试输出,我可以看到它LongRunningMethod一直运行到最后。

Ros*_*utt 10

看起来您正在将延续任务相互链接起来,而不是全部从原始任务中分离出来。这意味着您的 TaskContinuationOptions 指的是链中上一个任务的完成状态,而不是原始父级 (MyTask)。
我会尝试类似以下的东西(我不能在本地尝试这个确切的版本,因为我没有你的所有功能,但类似的东西对我有用)。

    MyTask = LongRunningMethod(mods, Settings, progressReporter, CancelSource.Token);

    MyTask.ContinueWith(e =>
    {
        Log.Info("OnlyOnCanceled");
    }, default ,TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());

    MyTask.ContinueWith(e =>
    {
        Log.Info("OnlyOnFaulted");
    }, default ,TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());

    MyTask.ContinueWith(e =>
    {
        Log.Info("OnlyOnRanToCompletion");
    }, default ,TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

    MyTask.ContinueWith(e =>
    {
        Log.Info("None");
    }, default ,TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
Run Code Online (Sandbox Code Playgroud)

这给了我:

OnlyOnRanToCompletion  
None
Run Code Online (Sandbox Code Playgroud)


Gur*_*ron 5

正如文档中所写:

您可以指定延续仅在前提成功完成时才运行,或者仅当它在故障状态下完成时才运行。如果在前提准备好调用延续时条件不为真,则延续直接转换到 TaskStatus.Canceled 状态,随后无法启动。

这意味着链接ContinueWith调用在您的情况下不起作用,因为如果第一次继续与实际任务状态不匹配,它将返回取消的任务到下一个链接调用。

您可以Task在此代码段中检查更改的结果和重新排序的延续:

var task = 
    //Task.FromCanceled(new CancellationToken(true))
    Task.FromException(new Exception())
    //Task.CompletedTask
        .ContinueWith(e => Console.WriteLine("OnlyOnCanceled"), TaskContinuationOptions.OnlyOnCanceled)
        .ContinueWith(e => Console.WriteLine("OnlyOnFaulted"), TaskContinuationOptions.OnlyOnFaulted)
        .ContinueWith(e => Console.WriteLine("OnlyOnRanToCompletion"), TaskContinuationOptions.OnlyOnRanToCompletion); 
Task.WaitAny(task); // to wait for task without exception and async
Console.WriteLine(task.Status);
Run Code Online (Sandbox Code Playgroud)

此外,设置多个单独的延续可能不是最佳解决方案,因为实际上您只需要一个任务时,您将产生多个任务。

同一文档的“将数据传递到延续”部分建议分析Task.Status先行词的属性,例如:

Task.FromResult(1)
    .ContinueWith(t => 
    {   
        switch (t.Status)
        {
            case TaskStatus.RanToCompletion: Console.WriteLine("OnlyOnRanToCompletion"); return t.Result;
            case TaskStatus.Canceled: Console.WriteLine("OnlyOnCanceled"); return default;
            case TaskStatus.Faulted: Console.WriteLine("OnlyOnFaulted"); return default;
            default: return default;
        }
    });
Run Code Online (Sandbox Code Playgroud)


The*_*ias 5

使用async-await进行日志记录可能会更简单。这样您就可以避免老式ContinueWith方法及其令人困惑的参数和行为。

public static async void OnCompletionLog(this Task task, string name)
{
    try
    {
        await task;
        Log.Info($"{name} RanToCompletion");
    }
    catch (OperationCanceledException)
    {
        Log.Info($"{name} Canceled");
    }
    catch (Exception ex)
    {
        Log.Error(ex, $"{name} Failed");
    }
}
Run Code Online (Sandbox Code Playgroud)