首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么ContinueWith返回Task<Task<x>>

为什么ContinueWith返回Task<Task<x>>
EN

Stack Overflow用户
提问于 2018-06-06 19:07:11
回答 3查看 136关注 0票数 0

我有一个方法,GetSomethingAsync,那就是return Task<MyTypeA>.Run(() => GetSomething());

我有另一个方法,GetSomethingElseAsync,那就是return Task<MyTypeB>.Run(() => GetSomethingElse());

我希望第二个任务有条件地运行,基于第一个任务中的一些内容,所以我有

代码语言:javascript
复制
var task1 = GetSomethingAsync();
var task2 = task1.ContinueWith(x =>
    x.Result == null ? Task.FromResult(null) : GetSomethingElseAsync());

task2编译为Task<Task<MyTypeB>>。我在等Task<MyTypeB>。有可能得到我预期的结果吗?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-06-06 19:19:40

假设

代码语言:javascript
复制
var task1 = GetSomethingAsync();

使用异步定义并返回一个Task<T>

ContinueWith()的签名是

代码语言:javascript
复制
public Task<TResult> ContinueWith<TResult>(
  Func<Task, TResult> continuationFunction
)

现在关注您的lambda结果:

代码语言:javascript
复制
x => x.Result == null ? Task.FromResult(null)

如果任务的结果为null,则返回一个任务(Task.FromResult)。

因此,您的Func<Task, TResult> TResult是一个Task<T>,就好像lambda是按以下方式编写的:

代码语言:javascript
复制
Task<T> AnonymousFunction(task x)
{
  return ... Task.FromResult(null);
}

现在,ContinueWith()的结果是一个Task<TResult>,既然我们已经确定TResult是一个Task<T>,那么结果类型就是Task<Task<T>>

票数 1
EN

Stack Overflow用户

发布于 2022-10-09 17:37:16

您需要的是Unwrap扩展方法。此方法接受嵌套的任务Task<Task<T>>,并将其扁平化为Task<T>。未包装任务表示外部和内部任务的完成。如果其中任何人失败,则未包装的任务包含发生在任一任务中的Exception

代码语言:javascript
复制
Task<MyTypeA> task1 = GetSomethingAsync();

Task<MyTypeB> task2 = task1.ContinueWith(t =>
{
    return t.Result is null ?
        Task.FromResult<MyTypeB>(null) : GetSomethingElseAsync();
}).Unwrap();

不幸的是,上面的代码远非完美。

  1. 它违反了CA2008:不要在没有传递TaskScheduler的情况下创建任务准则。
  2. 它将task1的取消传播为失败。因此,如果是task1.IsCanceled,则task2将是IsFaulted
  3. 它将task1的可能异常封装在嵌套的AggregateException中。

下面是更正的相同代码:

代码语言:javascript
复制
Task<MyTypeA> task1 = GetSomethingAsync();

Task<MyTypeB> task2 = task1.ContinueWith(t =>
{
    if (t.IsFaulted)
        return Task.FromException<MyTypeB>(t.Exception.InnerException);
    if (t.IsCanceled)
    {
        TaskCompletionSource<MyTypeB> tcs = new();
        tcs.SetCanceled(new TaskCanceledException(t).CancellationToken);
        return tcs.Task;
    }
    return t.Result is null ?
        Task.FromResult<MyTypeB>(null) : GetSomethingElseAsync();
}, TaskScheduler.Default).Unwrap();

一个更简单的解决方案是抛弃繁琐的ContinueWith方法,转而使用方便的异步/等待语言特性:

代码语言:javascript
复制
Task<MyTypeA> task1 = GetSomethingAsync();

Task<MyTypeB> task2 = ((Func<Task<MyTypeB>>)(async () =>
{
    MyTypeA result = await task1.ConfigureAwait(false);
    return result is null ? null :
        await GetSomethingElseAsync().ConfigureAwait(false);
}))();

这可以通过使用Then系列的扩展方法来进一步简化,这些方法可以找到这里 (由Stephen编写)。

代码语言:javascript
复制
Task<MyTypeA> task1 = GetSomethingAsync();

Task<MyTypeB> task2 = task1.Then(async result =>
    result is null ? null : await GetSomethingElseAsync().ConfigureAwait(false));

您可以将Then扩展方法看作是一个ContinueWithResult,其中包含一个参数,该参数包含先导任务的结果,而不是前置任务本身。

票数 0
EN

Stack Overflow用户

发布于 2018-06-06 19:27:19

我相信这就是你想要的:

代码语言:javascript
复制
var task2 = task1.ContinueWith(completedTask => completedTask.Result == null ? (TypeB)null : GetSomethingElse());

task2现在应该是Task<MyTypeB>类型。

问题是task1.ContinueWith已经返回了一个Task<T>。在您的语句中,您还将返回一个Task<T>,或者更准确地说,返回一个Task<MyTypeB>。因此,调用task1.ContinueWith的结果是Task<T>,其中TTask<MyTypeB>。您在调试器中看到的是:Task<Task<MyTypeB>>

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50727865

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档