怎么做 Task.ContinueWith<Task>(...)

How to do Task.ContinueWith<Task>(...)

我使用 HttpClient,我想写这样的东西:

HttpClient client = ...;
Task<string> getContent()
{
    return client.GetAsync(...)
   .ContinueWith( t => t.Result.Content.ReadAsStringAsync() );
}

我知道我可以写

.ContinueWith( t => t.Result.Content.ReadAsStringAsync().Result);

但是池中的一个线程将被阻塞。 我想要像 Google Task library.

中那样的 continueWithTask

我该如何完成?

更新

是的,我确实需要使用任务而不是 async/await,我真的知道我想要什么。

更新2

我修正了我的观点,现在我认为我选择的技术是错误的。如果有人怀疑,这里有一个 great example 好的代码。

这些天你应该避免 ContinueWith,更喜欢 async/await,除非你有 非常非常具体的原因;我怀疑这会起作用:

async Task<string> getContent()
{
    var foo = await client.GetAsync(...); // possibly with .ConfigureAwait(false)
    return await foo.Content.ReadAsStringAsync(); // possibly with .ConfigureAwait(false)
}

but one of pool's threads will be blocked

情况并非如此,因为 ContinueWith 委托只会在 "antecedent" 任务完成后调用。 Result 不会阻塞,甚至不需要同步。

第二个 .Result (ReadAsStringAsync().Result) 正在阻塞。所以你必须把它变成另一个 ContinueWith.

一般来说,一串连续的IOs会变成一串ContinueWith

一般来说,await 在这里更可取,但您表示这不适合您。

And yes, I do need to use Tasks instead of async/await and I really know what I want.

强烈推荐。我想不出不使用 async/await 的充分理由,除非你坚持使用 .NET 4.0(即 Windows XP)。事实并非如此,因为您使用的是 HttpClientTask.Run。所以请记住,这个答案纯粹是指导性的不推荐用于生产

ContinueWith 调用可以是 "chained",有点类似于 Promise.then 在 JavaScript 中的工作方式,但开箱即用的 C# 链接语义比 JavaScript 更尴尬。

一方面,Task<Task<T>> 类型不会自动展开。有一个 Unwrap 方法可用。另一方面,使用 .Result - 更自然地与 ContinueWith 一起使用的 TPL 遗留物 - 会将异常包装在 AggregateException 中,这可能会导致一种有趣的 "cascade" ,你的内部异常可以深埋在嵌套的 AggregateException 实例中。因此 AggregateException.Flatten 的存在是为了理顺事后的混乱局面。哦,你应该 always pass a TaskScheduler to ContinueWith.

这是第一次尝试,明确指定 TaskScheduler,使用 Unwrap 解包嵌套任务,并使用 GetAwaiter().GetResult() 而不是 Result 避免嵌套异常:

Task<string> getContent()
{
  // I am so sorry, future coder, but I cannot use await here because <insert good reason>
  return Task.Run(()=> client.GetAsync(...))
      .ContinueWith(t => t.GetAwaiter().GetResult().Content.ReadAsStringAsync(), TaskScheduler.Default).Unwrap()
      .ContinueWith(t => t.GetAwaiter().GetResult(), TaskScheduler.Default);
}

如果您在代码中经常这样做,您可能需要使用 something like .Then 来封装其中的一些逻辑。哦,一定要在评论中道歉;即使未来的维护者是您自己,对这样的代码也只是礼貌的做法。 ;)