C#有关CancellationToken学习

187 阅读3分钟

学习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,以确保程序在取消操作时的稳定性。