ContinueWith和TaskCancellation-如果任务失败,如何返回默认值

本文关键字:何返回 返回 默认值 TaskCancellation- 如果 任务 失败 ContinueWith | 更新日期: 2023-09-27 18:26:20

我读了一些关于TaskCancellations的线程。。然而,我找不到一个简单问题的解决方案:当我的任务失败时,如何获得默认值?

我不能(!)修改任务本身并在它周围放置try-catch包装器。我当然可以在wait周围放置try/catch,但如果可能的话,我想用ContinueWith来处理这个问题。

public Task<List<string>> LoadExample()
{
    Task<List<string>> task = LoadMyExampleTask();
    task.ContinueWith(t => default(List<string>), TaskContinuationOptions.OnlyOnFaulted);
    return task;
}

我认为这将是处理这个问题的正确方法。但是,我的应用程序抛出一个JsonParseException(在LoadMyExampleTask中调用)。我希望得到null或(甚至更好)一个空列表。

事实上,我想要的只是:

var emptyOrFilledList = await LoadExample(); // guaranteed no exception thrown

基于Luaan的出色回答,我编写了一个带有defaultValue选项的扩展方法:

public static Task<T> DefaultIfFaulted<T>(this Task<T> @this, T defaultValue = default(T))
{
   return @this.ContinueWith(t => t.IsCompleted ? t.Result : defaultValue);
}

编辑:await myTask.DefaultifFaulted()刚刚抛出一个

[ERROR]致命未处理异常:System.AggregateException

你确定每个异常都被捕获了吗?

ContinueWith和TaskCancellation-如果任务失败,如何返回默认值

如果需要,则不能返回原始任务-需要返回延续。

public Task<List<string>> LoadExample()
{
    Task<List<string>> task = LoadMyExampleTask();
    return task.ContinueWith(t => 
            t.IsFaulted || t.IsCanceled ? default(List<string>) : t.Result);
}

当原始任务出现故障时,您的原始代码确实允许继续运行,但您没有读取任务的状态-任务有一个处理错误的继续与原始任务上的await将做什么完全无关。

当然,把它变成一个通用的辅助方法是很容易的:

public static Task<T> DefaultIfFaulted<T>(this Task<T> @this)
{
  return @this.ContinueWith (t => t.IsCanceled || t.IsFaulted ? default(T) : t.Result);
}

正如承诺的那样,以下是名副其实的DefaultIfFaulted<T>变体(以及本问题的标题)。它们保留先行任务的行为,除非它出现故障(特别是,取消被传播,而不是被AggregateException忽略或屏蔽):

老派(.NET 4.0)方式:

public static Task<T> DefaultIfFaulted<T>(this Task<T> task)
{
    // The continuation simply returns the antecedent task unless it's faulted.
    Task<Task<T>> continuation = task.ContinueWith(
        t => (t.Status == TaskStatus.Faulted) ? Task.FromResult(default(T)) : t,
        TaskContinuationOptions.ExecuteSynchronously
    );
    return continuation.Unwrap();
}

异步/等待方式(简单但较慢):

public static async Task<T> DefaultIfFaulted<T>(this Task<T> task)
{
    try
    {
        return await task.ConfigureAwait(false);
    }
    catch (Exception ex) when (!(ex is OperationCanceledException))
    {
        return default(T);
    }
}

异步/等待方式(性能几乎与Unwrap相同):

public static async Task<T> DefaultIfFaulted<T>(this Task<T> task)
{
    // Await completion regardless of resulting Status (alternatively you can use try/catch).
    await task
        .ContinueWith(_ => { }, TaskContinuationOptions.ExecuteSynchronously)
        .ConfigureAwait(false);
    return task.Status != TaskStatus.Faulted
        // This await preserves the task's behaviour
        // in all cases other than faulted.
        ? await task.ConfigureAwait(continueOnCapturedContext: false)
        : default(T);
}

测试(通过以上所有测试):

using Xunit;
[Fact]
public async Task DefaultIfFaultedTest()
{
    var success = Task.Run(() => 42);
    var faulted = Task.Run(new Func<int>(() => { throw new InvalidOperationException(); }));
    Assert.Equal(42, await success.DefaultIfFaulted());
    Assert.Equal(0, await faulted.DefaultIfFaulted());
    await Assert.ThrowsAsync<TaskCanceledException>(() =>
    {
        var tcs = new TaskCompletionSource<int>();
        tcs.SetCanceled();
        return tcs.Task.DefaultIfFaulted();
    });
}