​用户向服务器发送HTTP请求应用程序页面是一种非常可能的情况。当我们的应用程序处理请求时,用户可以从该页面离开。在这种情况下,我们希望取消HTTP请求,因为响应对该用户不再重要。当然,这只是实际应用程序中可能发生的许多情况中的一种,我们希望取消请求。因在本文中,将学习如何使用CancellationToken取消客户端中的HTTP请求。

使用CancellationToken取消使用HttpClient发送的请求

在介绍中,我们指出,如果用户从页面离开,他们就不再需要响应,因此取消该请求是一个很好的做法。但还有更多的原因。HttpClient正在处理异步任务,因此取消一个不再需要的任务将释放我们用来运行任务的线程。这意味着该线程将被返回到一个线程池,在该线程池中,该线程可以用于其他一些工作。这肯定会提高应用程序的可伸缩性。

当然,我们不能就这样取消请求。要执行这样的操作,我们必须使用CancellationTokenSource和CancellationToken。

我们使用CancellationTokenSource来创建CancellationToken,并通知所有CancellationToken的消费者请求已被取消。在我们的例子中,HttpClient将使用CancellationToken并监听通知。一旦收到请求取消通知,我们将使用HttpClient取消该请求。

使用HttpClient实现CancellationToken

我们要做的第一件事是为这个示例创建一个新service:

public class HttpClientCancellationService : IHttpClientServiceImplementation
{
private static readonly HttpClient _httpClient = new HttpClient();
private readonly JsonSerializerOptions _options;

public HttpClientCancellationService()
{
_httpClient.BaseAddress = new Uri("https://localhost:5001/api/");
_httpClient.Timeout = new TimeSpan(0, 0, 30);
_httpClient.DefaultRequestHeaders.Clear();

_options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
}

public async Task Execute()
{
throw new NotImplementedException();
}
}

我们创建了一个HttpClient实例并为其提供配置。同样,对JSON序列化也做同样的事情。在下一篇文章中,我们将学习关于HttpClientFactory的知识,并了解如何将这个配置移动到一个单独的位置,而不会在所有文件中重复它,还将学习如何解决HttpClient可能导致的问题。现在,我们将保持现状。

现在,让我们添加一个新方法来获取所有的公司数据:

private async Task GetCompaniesAndCancel()
{
using (var response = await _httpClient.GetAsync("companies", HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
var companies = await JsonSerializer.DeserializeAsync>(stream, _options);
}
}

这也是上一篇文章中熟悉的代码,这里不做解释。现在,假设我们想取消这个请求。正如之前说过的,要取消一个请求,我们需要CancellationTokenSource。那么,让我们来实现它:

private async Task GetCompaniesAndCancel()
{
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(2000);

using (var response = await _httpClient.GetAsync("companies",
HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token))
{
response.EnsureSuccessStatusCode();

var stream = await response.Content.ReadAsStreamAsync();

var companies = await JsonSerializer.DeserializeAsync>(stream, _options);
}
}

这里,我们创建了一个新的cancellationTokenSource对象。在创建对象之后,我们希望取消请求。这通常是由用户执行的——通过按下取消按钮或离开一个页面,要取消请求,可以使用两个方法:Cancel()和CancelAfter(),前者会立即取消请求。在本例中,我们使用CancelAfter方法并提供两秒作为参数。最后,我们必须通知HttpClient取消操作。为此,我们提供一个取消令牌作为GetAsync的附加参数。

我们现在就可以测试一下。

测试取消请求

在启动应用程序之前,我们需要确保应用程序启动时调用了我们的方法。为此,我们必须修改Execute方法:

public async Task Execute()
{
await GetCompaniesAndCancel();
}

同时,我们必须在Program类中注册这个服务:

private static void ConfigureServices(IServiceCollection services)
{
//services.AddScoped HttpClientCrudService>();
//services.AddScoped HttpClientPatchService>();
//services.AddScoped HttpClientStreamService>();
services.AddScoped HttpClientCancellationService>();
}

现在,让我们启动这两个应用程序:

可以看到我们的请求被取消了。

通过共享CancellationToken改进解决方案

目前的实现对于我们的学习示例非常有用。但在实际的应用程序中,我们希望能够通过将令牌传递给所有请求达到取消不同请求的目的。这将允许在需要时取消所有这些请求。此外,我们希望能够从应用程序的不同部分访问这个CancellationTokenSource,例如当用户单击取消按钮或从页面离开时。在这种情况下,我们不想把CancellationTokenSource隐藏在单个方法中。

private static readonly HttpClient _httpClient = new HttpClient();
private readonly JsonSerializerOptions _options;
private readonly CancellationTokenSource _cancellationTokenSource;

public HttpClientCancellationService()
{
_httpClient.BaseAddress = new Uri("https://localhost:5001/api/");
_httpClient.Timeout = new TimeSpan(0, 0, 30);
_httpClient.DefaultRequestHeaders.Clear();

_options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };

_cancellationTokenSource = new CancellationTokenSource();
}

这里,我们创建了一个CancellationTokenSource只读变量,并在构造函数中实例化它。然后,我们要修改Execute方法:

public async Task Execute()
{
_cancellationTokenSource.CancelAfter(2000);
await GetCompaniesAndCancel(_cancellationTokenSource.Token);
}

在这个方法中,我们调用CancelAfter方法来指定要取消请求的周期,并将令牌传递给GetCompaniesAndCancel方法。当然,我们还必须修改GetCompaniesAndCancel方法:

private async Task GetCompaniesAndCancel(CancellationToken token)
{
using (var response = await _httpClient.GetAsync("companies",
HttpCompletionOption.ResponseHeadersRead, token))
{
response.EnsureSuccessStatusCode();

var stream = await response.Content.ReadAsStreamAsync();

var companies = await JsonSerializer.DeserializeAsync>(stream, _options);
}
}

此时,我们的方法接受令牌并使用它来侦听取消通知。现在,可以重新启动API和客户端应用。

可以继续看看如何在我们的应用程序中处理这个异常。

处理TaskCanceledException

如果我们想处理应用程序在取消请求后抛出的异常,只需将请求封装在try-catch块中:

private async Task GetCompaniesAndCancel(CancellationToken token)
{
try
{
using (var response = await _httpClient.GetAsync("companies",
HttpCompletionOption.ResponseHeadersRead, token))
{
response.EnsureSuccessStatusCode();

var stream = await response.Content.ReadAsStreamAsync();

var companies = await JsonSerializer.DeserializeAsync>(stream, _options);
}
}
catch (OperationCanceledException ocex)
{
Console.WriteLine(ocex.Message);
}
}

我们看到应用程序抛出了TaskCanceledException,但是因为它继承了OperationCanceledException类,所以我们可以使用这个类来捕获异常。当然,在catch块中,我们可以执行许多操作,但对于本例来说,只记录消息就足够了。现在,让我们启动这两个应用程序并检查结果:

检查响应的状态代码

使用我们现在的实现,如果响应不成功,我们将抛出异常。为了达到100%的准确性,EnsureSuccessStatusCode()方法将执行此操作。但在许多情况下,我们希望根据响应失败的真正原因提示更用户友好的消息。我们可以检查响应的状态码。也就是说,这里我们将使用一种状态代码,并展示如何用更有意义的消息提供更好的用户体验。

对于本例,我们将使用HttpClientStreamService类。让我们在这个类中创建一个新方法:

private async Task GetNonExistentCompany()
{
var uri = Path.Combine("companies", "F8088E81-7EFA-4E49-F824-08D8C38D155C");
using (var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();

var stream = await response.Content.ReadAsStreamAsync();

var companies = await JsonSerializer.DeserializeAsync>(stream, _options);
}
}

现在我们对整个代码已经很熟悉了,这里提供的Id 不存在于我们的数据库中。因此,我们的API应该返回404。在测试它之前,我们必须修改Execute方法:

public async Task Execute()
{
//await GetCompaniesWithStream();
//await CreateCompanyWithStream();
await GetNonExistentCompany();
}

同时,我们必须在Program类中启用这个服务:

private static void ConfigureServices(IServiceCollection services)
{
//services.AddScoped HttpClientCrudService>();
//services.AddScoped HttpClientPatchService>();
services.AddScoped HttpClientStreamService>();
//services.AddScoped HttpClientCancellationService>();
}

让我们启动两个应用程序并检查结果:

我们确实得到了404响应,但仍然抛出异常。我们可以改变这一点。

使用状态码

对我们的方法做一个小小的修改:

private async Task GetNonExistentCompany()
{
var uri = Path.Combine("companies", "F8088E81-7EFA-4E49-F824-08D8C38D155C");
using (var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead))
{
if(!response.IsSuccessStatusCode)
{
if (response.StatusCode.Equals(HttpStatusCode.NotFound))
{
Console.WriteLine("The company you are searching for couldn't be found.");
return;
}

response.EnsureSuccessStatusCode();
}

var stream = await response.Content.ReadAsStreamAsync();

var companies = await JsonSerializer.DeserializeAsync>(stream, _options);
}
}

首先检查响应是否包含带有IsSuccessStatusCode属性的成功状态代码。

如果没有,则显式检查我们想要处理的状态代码,在本例中是NotFound状态代码。在这种情况下,只需向控制台窗口写入一条信息消息。对于所有其他不成功的状态代码,用EnsureSuccessStatusCode方法抛出一个异常。当然,也可以使用其他状态代码来扩展这个条件,但在这种情况下,最好将该逻辑提取到另一个方法中,以使该方法更具可读性。现在,如果我们启动应用程序:

结论

现在,我们知道了如何使用CancellationToken和CancellationTokenSource取消请求,以及如何使用CancellationTokenSource在不同的请求之间共享令牌。此外,我们还知道如何使用响应中的不同状态代码来防止为每个不成功的响应抛出异常。

在下一篇文章中,我们将学习更多关于HttpClientFactory的内容,并看看这种方法的优点是什么。

原文链接:https://code-maze.com/canceling-http-requests-in-asp-net-core-with-cancellationtoken/

在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求的更多相关文章

  1. 在ASP.NET Core中用HttpClient(一)——获取数据和内容

    在本文中,我们将学习如何在ASP.NET Core中集成和使用HttpClient.在学习不同HttpClient功能的同时使用Web API的资源.如何从Web API获取数据,以及如何直接使用Ht ...

  2. 在ASP.NET Core中用HttpClient(二)——发送POST, PUT和DELETE请求

    在上一篇文章中,我们已经学习了如何在ASP.NET Core中使用HttpClient从Web API获取数据.此外,我们还学习了如何使用GetAsync方法和HttpRequestMessage类发 ...

  3. 在ASP.NET Core中用HttpClient(三)——发送HTTP PATCH请求

    在前面的两篇文章中,我们讨论了很多关于使用HttpClient进行CRUD操作的基础知识.如果你已经读过它们,你就知道如何使用HttpClient从API中获取数据,并使用HttpClient发送PO ...

  4. 在ASP.NET Core中用HttpClient(四)——提高性能和优化内存

    到目前为止,我们一直在使用字符串创建请求体,并读取响应的内容.但是我们可以通过使用流提高性能和优化内存.因此,在本文中,我们将学习如何在请求和响应中使用HttpClient流. 什么是流 流是以文件. ...

  5. 在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory

    ​到目前为止,我们一直直接使用HttpClient.在每个服务中,我们都创建了一个HttpClient实例和所有必需的配置.这会导致了重复代码.在这篇文章中,我们将学习如何通过使用HttpClient ...

  6. C# ASP.NET Core使用HttpClient的同步和异步请求

    引用 Newtonsoft.Json // Post请求 public string PostResponse(string url,string postData,out string status ...

  7. WPF中的常用布局 栈的实现 一个关于素数的神奇性质 C# defualt关键字默认值用法 接口通俗理解 C# Json序列化和反序列化 ASP.NET CORE系列【五】webapi整理以及RESTful风格化

    WPF中的常用布局   一 写在开头1.1 写在开头微软是一家伟大的公司.评价一门技术的好坏得看具体的需求,没有哪门技术是面面俱到地好,应该抛弃对微软和微软的技术的偏见. 1.2 本文内容本文主要内容 ...

  8. 帅呆了!ASP.NET Core每秒能处理115万个请求

    今天看到一篇英文博文 -- ASP.NET Core – 2300% More Requests Served Per Second,被震撼了!ASP.NET Core每秒能处理115万个请求(是的, ...

  9. [理解ASP.NET Core框架]一个五十行的控制台Web

    在阅读了Artech的ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程之后, 自己做了一个"迷你版"中的"迷你版" ...

随机推荐

  1. 硬盘测试工具fio用法总结

    一  fio介绍 linux下的一种常用的磁盘测试工具,支持裸盘和文件形式进行测试   二  硬盘测试常用名词 延迟:io的发起到返回写入成功的时间成为延迟,fio中延迟分为lat,slat,clat ...

  2. CSON vs JSON

    CSON vs JSON 今天在github浏览资料时,无意发现了这个很像json,却优于json的cson.故,再次分享给大家! 官方fork文档:https://github.com/xgqfrm ...

  3. TypedArray & ArrayBuffer

    TypedArray & ArrayBuffer Type Each element size in bytes Int8Array 1 Uint8Array 1 Uint8ClampedAr ...

  4. MDN Browser Compatibility Report 2020

    MDN Browser Compatibility Report 2020 top pain point https://mdn-web-dna.s3-us-west-2.amazonaws.com/ ...

  5. web development all in one

    web development all in one https://javascript.xgqfrms.xyz/web-development-all-in-one.html refs https ...

  6. JavaScript Inheritance All in One

    JavaScript Inheritance All in One constructor inheritance prototype chain inheritance "use stri ...

  7. WebView & WKWebView & UIWebView

    WebView & WKWebView & UIWebView WebView WKWebView https://developer.apple.com/documentation/ ...

  8. std::vector与std::list效能对比(基于c++11)

    测试对象类型不同,数量级不同时,表现具有差异: 测试数据对象为std::function时: test: times(1000)vector push_back time 469 usvector e ...

  9. Github Action 快速上手指南

    前言 各位读者,新年快乐,我是过了年匆忙赶回上海努力搬砖的蛮三刀. Github之前更新了一个Action功能(应该是很久以前了),可以实现很多自动化操作.用来替代用户自己设置的自动化脚本(比如:钩子 ...

  10. 1071 Speech Patterns——PAT甲级真题

    1071 Speech Patterns People often have a preference among synonyms of the same word. For example, so ...