学习X组件,看到以下代码
/// <summary>上传文件以及表单数据</summary>
/// <param name="client">Http客户端</param>
/// <param name="requestUri">请求资源地址</param>
/// <param name="fileName">目标文件名</param>
/// <param name="data">其它表单数据</param>
/// <param name="cancellationToken">取消通知</param>
public static async Task<String> UploadFileAsync(this HttpClient client, String requestUri, String fileName, Object? data = null, CancellationToken cancellationToken = default)
{
var content = new MultipartFormDataContent();
if (!fileName.IsNullOrEmpty())
content.Add(new StreamContent(fileName.AsFile().OpenRead()), "file", Path.GetFileName(fileName));
if (data != null)
{
foreach (var item in data.ToDictionary())
{
//if (item.Value == null) continue;
if (item.Value is String str)
content.Add(new StringContent(str), item.Key);
else if (item.Value is Byte[] buf)
content.Add(new ByteArrayContent(buf), item.Key);
else if (item.Value == null || item.Value.GetType().IsBaseType())
content.Add(new StringContent(item.Value + ""), item.Key);
else
content.Add(new StringContent(item.Value.ToJson()), item.Key);
}
}
return await PostAsync(client, requestUri, content, null, cancellationToken).ConfigureAwait(false);
}
private static async Task<String> PostAsync(HttpClient client, String requestUri, HttpContent content, IDictionary<String, String>? headers, CancellationToken cancellationToken)
{
var request = new HttpRequestMessage(HttpMethod.Post, requestUri)
{
Content = content
};
if (headers != null)
{
foreach (var item in headers)
{
request.Headers.Add(item.Key, item.Value);
}
}
// 设置接受 mediaType
if (content.Headers.TryGetValues("Content-Type", out var vs))
{
// application/json; charset=utf-8
var type = vs.FirstOrDefault()?.Split(';').FirstOrDefault();
if (type.EqualIgnoreCase("application/json", "application/xml")) request.Headers.Accept.ParseAdd(type);
}
// 开始跟踪,注入TraceId
using var span = Tracer?.NewSpan(request);
//if (span != null) span.SetTag(content.ReadAsStringAsync().Result);
var filter = Filter;
try
{
if (filter != null) await filter.OnRequest(client, request, null, cancellationToken).ConfigureAwait(false);
var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (filter != null) await filter.OnResponse(client, response, request, cancellationToken).ConfigureAwait(false);
#if NET5_0_OR_GREATER
var result = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
// 增加埋点数据
span?.AppendTag(result);
return result;
}
catch (Exception ex)
{
// 跟踪异常
span?.SetError(ex, null);
if (filter != null) await filter.OnError(client, ex, request, cancellationToken).ConfigureAwait(false);
throw;
}
}
对其中 CancellationToken用法没有看懂,于是问了一下AI。
以下是对 UploadFileAsync 方法中 cancellationToken 参数的详细解释:
1. 总体功能
cancellationToken是CancellationToken类型的参数,它主要用于在异步操作过程中提供一种机制,允许外部调用者取消该异步操作。
2. 在异步操作中的使用
- 在
UploadFileAsync这个方法中,cancellationToken作为参数传递给PostAsync方法。 PostAsync方法可能会在执行长时间运行的网络请求(如文件上传)时,检查cancellationToken的状态,以确定是否应该提前终止操作。
3. 实现细节
public static async Task<String> UploadFileAsync(this HttpClient client, String requestUri, String fileName, Object? data = null, CancellationToken cancellationToken = default)
{
var content = new MultipartFormDataContent();
if (!fileName.IsNullOrEmpty())
content.Add(new StreamContent(fileName.AsFile().OpenRead()), "file", Path.GetFileName(fileName));
if (data!= null)
{
foreach (var item in data.ToDictionary())
{
//if (item.Value == null) continue;
if (item.Value is String str)
content.Add(new StringContent(str), item.Key);
else if (item.Value is Byte[] buf)
content.Add(new ByteArrayContent(buf), item.Key);
else if (item.Value == null || item.Value.GetType().IsBaseType())
content.Add(new StringContent(item.Value + ""), item.Key);
else
content.Add(new StringContent(item.Value.ToJson()), item.Key);
}
}
return await PostAsync(client, requestUri, content, null, cancellationToken).ConfigureAwait(false);
}
代码解释:
-
CancellationToken cancellationToken = default:- 这里使用了默认值
default,表示如果调用者没有显式传递cancellationToken,将使用CancellationToken.None,表示不支持取消操作。
- 这里使用了默认值
-
await PostAsync(client, requestUri, content, null, cancellationToken).ConfigureAwait(false);:- 在调用
PostAsync方法时,将cancellationToken传递进去。 PostAsync方法内部可以使用cancellationToken.ThrowIfCancellationRequested()方法检查是否请求取消操作,如果取消操作被请求,该方法将抛出OperationCanceledException,从而终止异步操作。
- 在调用
4. 如何使用 cancellationToken
- 调用
UploadFileAsync方法时,可以创建一个CancellationTokenSource并获取其CancellationToken,将该CancellationToken传递给UploadFileAsync方法。
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
var fileName = "example.txt";
var data = new { key = "value" };
var cts = new CancellationTokenSource();
// 假设在 5 秒后取消操作
cts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
var result = await client.UploadFileAsync("http://example.com/upload", fileName, data, cts.Token);
Console.WriteLine(result);
}
catch (OperationCanceledException)
{
Console.WriteLine("Upload operation was cancelled.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
}
代码解释:
var cts = new CancellationTokenSource();:创建CancellationTokenSource实例,它可以用来控制取消操作。cts.CancelAfter(TimeSpan.FromSeconds(5));:在 5 秒后自动请求取消操作。await client.UploadFileAsync("http://example.com/upload", fileName, data, cts.Token);:将cts.Token传递给UploadFileAsync方法。- 如果在 5 秒内
UploadFileAsync方法还未完成,且PostAsync方法内部检查cancellationToken.ThrowIfCancellationRequested(),将抛出OperationCanceledException,并被catch (OperationCanceledException)捕获,输出 "Upload operation was cancelled."。
5. 好处
- 资源释放:当用户想要取消一个长时间运行的操作时,使用
cancellationToken可以避免不必要的资源浪费,例如网络请求的持续占用和服务器资源的消耗。 - 用户体验:对于长时间运行的操作,用户可以手动或自动取消操作,提高了用户体验,避免用户长时间等待无法终止的操作。
总结
cancellationToken在UploadFileAsync方法中用于支持异步操作的取消功能。- 通过
CancellationTokenSource创建和控制CancellationToken,并传递给异步方法,在方法内部可以根据该CancellationToken的状态决定是否终止操作。 - 在使用
cancellationToken时,要注意异常处理,如OperationCanceledException,以确保程序在取消操作时的稳定性。