3

我正在创建一个与实时 Web API 对话的网络客户端。

客户端每秒必须进行许多不同的调用Task<TResult>并向每个客户端组件反馈,以便客户端可以决定是否阻塞:

public Task<TResult> Execute<TResult>(IOperation<TResult> operation);

进行 API 调用的过程如下所示:

  1. 将(小,小于 1KB)请求序列化到 Json
  2. 使用HttpClient
  3. 成功后,反序列化为TResult(Json 的大小可能为几百 KB,但通常要小得多)并返回

在我的测试中,选择在任务工作流中包含每个步骤的位置(以及因此在哪个线程上)对性能有重大影响。

到目前为止我发现的最快的设置是这样的(半伪代码,为简洁起见省略了泛型类型参数):

// 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 最有效地使用可用线程。

那么,是否有一个关于什么应该进入任务以及什么应该放在它之外或继续的一般规则?

在这种特定情况下,我可以尝试进一步的优化吗?

4

2 回答 2

7

您根本不需要使用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,然后在其上发布新的延续以获得最终字符串。

于 2013-03-04T18:27:47.143 回答
2

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();
于 2013-03-04T18:31:09.143 回答