创建一个完成的 Task<T>。

142
我正在实现一个方法Task<Result> StartSomeTask(),并在调用该方法之前已经知道了结果。我该如何创建一个已经完成的Task<T>
这是我目前正在做的:
private readonly Result theResult = new Result();

public override Task<Result> StartSomeTask()
{
    var task = new Task<Result>(() => theResult);
    task.RunSynchronously(CurrentThreadTaskScheduler.CurrentThread);
    return task;
}

有更好的解决方案吗?


6
请注意,这个问题的答案同样适用于创建普通任务(没有<T>),因为 Task<T> 继承自 Task。 - Tim Lovell-Smith
请注意,现在有ValueTask用于已完成的任务(即对于您已经拥有的值,使代码本质上是同步的),这将节省您的分配。 - nawfal
6个回答

228

在针对 .NET 4.5 的情况下,您可以使用Task.FromResult

public static Task<TResult> FromResult<TResult>(TResult result);
使用 Task.FromException 创建一个失败的任务:
public static Task FromException(Exception exception);
public static Task<TResult> FromException<TResult>(Exception exception);

.NET 4.6增加了Task.CompletedTask,如果您需要一个非泛型的Task

public static Task CompletedTask { get; }

针对较旧版本的 .NET 的解决办法:

  • 当使用 Async Targetting Pack (或 AsyncCTP) 并且目标是 .NET 4.0 时,可以使用 TaskEx.FromResult

  • 在 .NET 4.6 之前获取非泛型的 Task,可以利用 Task<T> 继承自 Task 的特性,只需调用 Task.FromResult<object>(null)Task.FromResult(0)


13
为了返回一个非泛型任务,最好使用类似于Task.FromResult(0)的语法。使用“null”作为参数可能会让编译器混淆,因为它无法确定泛型参数。 - Whyllee
异常处理怎么办?异步方法被编译成状态机,它会捕获异常并将其保存在返回的任务中。即使是在第一个 await 之前执行的代码也会这样。返回 Task.FromResult 的方法可能会直接抛出异常。 - Robert Važan
一个有趣的边缘案例。可以说,如果您从方法中检索到您已知的结果,并且该方法引发异常,则存在需要修复的缺陷。 - Gusdor
1
@RobertVažan 你可以轻松地编写自己的 FromException 方法,它的行为类似于 FromResult,但代表了一个失败的任务。如果异常在结果任务中被表示很重要,这样的方法可以简单地返回其错误情况。 - Servy
1
Task.FromException在.NET 4.5中不可用...我认为应该进行说明。 - STiLeTT

115
private readonly Result theResult = new Result();

public override Task<Result> StartSomeTask()
{
    var taskSource = new TaskCompletionSource<Result>();
    taskSource.SetResult(theResult);
    return taskSource.Task;
}

1
@DanielLobo 如果您解释一下您的反对意见,您可能会得到一个答案。 - user2023861
2
下面那个不是更简单,而且赞数更多吗?@user2023861 - Daniel Lobo

11

对于没有返回值的任务,.NET 4.6 添加了 Task.CompletedTask

它返回一个已经处于状态TaskStatus.RanToCompletion的任务。它可能每次返回相同的实例,但文档警告您不要依赖这个事实。


1
考虑到问题特别涉及到 Task<T>,最好将其作为注释。 - Mr. Boy

2
如果您正在使用Rx,另一种选择是Observable.Return(result).ToTask()。

1
调用Task.WhenAll而不带任何参数会返回一个已完成的任务。
Task task = Task.WhenAll();

虽然这样做可以起作用,但它是一种晦涩的解决方法,可能会让人们在阅读代码时感到困惑,因为它暗示着等待不存在的任务。 - Adrian Hristov

0
你可以尝试使用 var myAlreadyCompletedTask = Task.FromResult<string>("MyValue"),这将为你提供一个指定返回类型的任务。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接