在同步 class 库中使用 HttpClient
Using HttpClient in the synchronous class library
我正在为第 3 方 API 编写一个 class 库包装器,使用 .Net Standard,稍后我打算在我的其他项目中使用这个包装器。在浏览网络时,我发现人们普遍关心的是应该使用 HttpClient class 来发出 HTTP 请求。
我知道我可以采取两种方法:
- 使用 async/await 一直到客户端项目
- 使用 Task.Wait() 方法
到目前为止,我将采用第一种方法。但这两种方法似乎都有问题。第一个比同步方法的可重用性低,同步方法 return 它们的类型而不是 Task 对象。我宁愿采用第二种方法,但它容易出现死锁。
我发出单个请求的代码(使用无参数构造函数初始化的 HttpClient):
protected async Task<JObject> Request(string url)
{
Uri uri = BuildUrl(url);
HttpResponseMessage response = await HttpClient.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
return JObject.Parse(result);
}
return new JObject();
}
对于多个请求:
protected async Task<JObject[]> Request(IEnumerable<string> urls)
{
var requests = urls.Select(Request);
return await Task.WhenAll(requests);
}
包装器中的用法class:
protected async Task<JObject> RequestGet(string id, bool byUrl)
{
if (IsBulk && !byUrl)
return await Request($"{Url}?id={id}");
if (byUrl)
return await Request(Url + id);
return await Request(Url);
}
我如何修改代码(第 1 和第 2 个代码段),以便它不会在每个调用方方法(第 3 个代码段)中导致任何死锁和异步使用?
一直使用 async
是正确的方法,您可以 return 从 Task
方法中得到结果(换句话说——如您自己的代码所示——说他们 return 除了 Task 什么都不做是不对的:
public async Task<string> GetString(int value)
{
return value.ToString();
}
显然不需要 async
,它不需要 await
任何东西,但关键是 Task
可以 return 任何东西感谢仿制药。您甚至可以使用新奇的 C# 7 值元组:
public async Task<(string name, int age)> GetUserInfo(int userId) { ... }
不支持同步使用 HttpClient 并且容易出现死锁,您无法执行任何操作(包括使用 Task.Wait)来改变这一事实。您有 2 个选择:
仅支持异步。
改用旧的 WebRequest APIs 并以这种方式支持同步调用。
我会选择选项 1。最新最好的 HTTP 库甚至不再支持同步 I/O 是有原因的。较新的多核处理器和分布式架构浪潮引发了编程世界的范式转变,在等待 I/O 时占用线程(尤其是更长的 运行 网络调用,如 HTTP)总计资源浪费。当像 C# 这样的语言为开箱即用的异步编程提供令人难以置信的支持时,几乎没有充分的理由再进行同步 HTTP。大多数开发人员都明白这一点,我认为您不应该担心仅通过异步来放弃用户。相反,鼓励他们以正确的方式加快速度。
我正在为第 3 方 API 编写一个 class 库包装器,使用 .Net Standard,稍后我打算在我的其他项目中使用这个包装器。在浏览网络时,我发现人们普遍关心的是应该使用 HttpClient class 来发出 HTTP 请求。
我知道我可以采取两种方法:
- 使用 async/await 一直到客户端项目
- 使用 Task.Wait() 方法
到目前为止,我将采用第一种方法。但这两种方法似乎都有问题。第一个比同步方法的可重用性低,同步方法 return 它们的类型而不是 Task 对象。我宁愿采用第二种方法,但它容易出现死锁。
我发出单个请求的代码(使用无参数构造函数初始化的 HttpClient):
protected async Task<JObject> Request(string url)
{
Uri uri = BuildUrl(url);
HttpResponseMessage response = await HttpClient.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
return JObject.Parse(result);
}
return new JObject();
}
对于多个请求:
protected async Task<JObject[]> Request(IEnumerable<string> urls)
{
var requests = urls.Select(Request);
return await Task.WhenAll(requests);
}
包装器中的用法class:
protected async Task<JObject> RequestGet(string id, bool byUrl)
{
if (IsBulk && !byUrl)
return await Request($"{Url}?id={id}");
if (byUrl)
return await Request(Url + id);
return await Request(Url);
}
我如何修改代码(第 1 和第 2 个代码段),以便它不会在每个调用方方法(第 3 个代码段)中导致任何死锁和异步使用?
一直使用 async
是正确的方法,您可以 return 从 Task
方法中得到结果(换句话说——如您自己的代码所示——说他们 return 除了 Task 什么都不做是不对的:
public async Task<string> GetString(int value)
{
return value.ToString();
}
显然不需要 async
,它不需要 await
任何东西,但关键是 Task
可以 return 任何东西感谢仿制药。您甚至可以使用新奇的 C# 7 值元组:
public async Task<(string name, int age)> GetUserInfo(int userId) { ... }
不支持同步使用 HttpClient 并且容易出现死锁,您无法执行任何操作(包括使用 Task.Wait)来改变这一事实。您有 2 个选择:
仅支持异步。
改用旧的 WebRequest APIs 并以这种方式支持同步调用。
我会选择选项 1。最新最好的 HTTP 库甚至不再支持同步 I/O 是有原因的。较新的多核处理器和分布式架构浪潮引发了编程世界的范式转变,在等待 I/O 时占用线程(尤其是更长的 运行 网络调用,如 HTTP)总计资源浪费。当像 C# 这样的语言为开箱即用的异步编程提供令人难以置信的支持时,几乎没有充分的理由再进行同步 HTTP。大多数开发人员都明白这一点,我认为您不应该担心仅通过异步来放弃用户。相反,鼓励他们以正确的方式加快速度。