我正在创建一个与实时web API对话的网络客户端。
客户端必须每秒进行许多不同的调用,并将Task<TResult>
反馈给每个客户端组件,以便客户端可以决定是否阻塞:
public Task<TResult> Execute<TResult>(IOperation<TResult> operation);
调用API的过程是这样的:
- 序列化(小的,小于1KB)请求到Json
- 使用
HttpClient
发送请求 - 成功后,反序列化到
TResult
(Json的大小可能是几百KB,但通常要小得多)并返回
在我的测试中,选择在Task工作流中包含每个步骤的位置(以及在哪个线程上)对性能有重大影响。
到目前为止,我发现最快的设置是这样的(半伪代码,为简洁起见省略了泛型类型参数):// serialize on main thread
var requestString = JsonConvert.SerializeObject(request);
// create message - omitted
var post = Task.Factory.StartNew(() => this.client.SendAsync(requestMessage)).Unwrap();
return post.ContinueWith(response =>
{
var jsonString = response.Result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(jsonString.Result);
});
最慢的是在单个任务中执行整个进程的设置:
return Task.Factory.StartNew((request) =>
{
var requestString = JsonConvert.SerializeObject(request);
// create message - omitted
var post = client.SendAsync(requestMessage);
var jsonString = post.Result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(jsonString.Result);
})
我本以为最后一个方法可能是最快的,因为你每个请求创建一个后台线程。我的假设是,由于阻塞调用,这不允许TPL最有效地利用可用线程。
那么,对于任务中应该包含哪些内容,哪些内容应该放在任务之外或延续中,是否存在一般规则?
在这种特殊情况下,我可以尝试任何进一步的优化吗?
您根本不需要使用Task.Factory.StartNew
,因为SendAsync
已经返回Task
:
var post = this.client.SendAsync(requestMessage);
return post.ContinueWith(response =>
{
var jsonString = response.Result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(jsonString.Result);
});
这实际上会更有效率,因为它根本不需要ThreadPool线程。
请注意,您可以使用async
/await
进一步优化这一点(以保持响应异步):
var response = await this.client.SendAsync(requestMessage);
var jsonString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(jsonString);
这也可以通过TPL延续来编写,但这需要从ReadAsStringAsync
返回(未包装的)任务,然后在其上发布一个新的延续以获得最终字符串。
所以,首先,在发送初始请求时没有理由使用StartNew
。你已经有了一个任务,在后台线程中启动任务是不必要的开销。
:
var post = Task.Factory.StartNew(() => this.client.SendAsync(requestMessage)).Unwrap();
可以成为:
var post = this.client.SendAsync(requestMessage);
接下来,在这两种情况下,都是阻塞ReadAsStringAsync
方法的结果,而不是异步处理它。这是在吞噬另一个线程池,线程只是坐在那里什么也不做。
应该这样做:
return post.ContinueWith(response =>
{
return response.Result.Content.ReadAsStringAsync()
.ContinueWith(t => JsonConvert.DeserializeObject(t.Result));
}).UnWrap();