我使用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。
我该怎么做?
更新
是的,我确实需要使用Tasks而不是async / await,我真的知道我想要什么。
UPDATE2
我修改了看法,现在我认为选择技术是错误的。如果有人怀疑,这里是great example的好代码。
答案 0 :(得分:6)
这几天,除非有非常具体的原因,否则应避免使用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)
}
答案 1 :(得分:3)
是的,我确实需要使用Tasks而不是async / await,我真的知道我想要什么。
我强烈推荐Marc's answer。除非您坚持使用.NET 4.0(即Windows XP),否则我不认为不使用async
/ await
的充分理由。事实并非如此,因为您正在使用HttpClient
和Task.Run
。因此,请记住,此答案是纯粹是说明性的,并且不建议用于生产环境。
ContinueWith
调用可以被“链接”,类似于Promise.then
在JavaScript中的工作方式,但是现成的C#链接语义比JavaScript笨拙。
一方面,Task<Task<T>>
类型不会自动解包。有Unwrap
方法可用。另外,使用.Result
(更自然地与ContinueWith
一起使用的TPL遗物)会将异常包装在AggregateException
中,这可能会引起一种有趣的“级联”,其中您的内部异常可以被深埋在嵌套的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
来封装其中的某些逻辑。哦,一定要在评论中道歉。即使将来的维护者是你自己,这也是像这样的代码有礼貌的事情。 ;)
答案 2 :(得分:0)
但是池的线程之一将被阻塞
情况并非如此,因为仅在“先行”任务完成后才调用ContinueWith
委托。 Result
不会阻止,甚至没有同步。
第二个.Result
(ReadAsStringAsync().Result
)被阻止了。因此,您必须将其转换为另一个ContinueWith
。
通常,顺序的IO序列变成ContinueWith
的序列。
通常,这里最好使用await
,但您表示这对您不起作用。