Skip to content
Bitzsoft.Integrationsbitzsoft.integrations

Concept

取消令牌传播

CancellationToken 在连接器库中的传播约定和 OperationCanceledException 处理规范。

Last updated

取消是控制流语义,不是失败。CancellationToken 必须正确传播,OperationCanceledException 绝不能被吞。

传播约定

接口定义

所有异步方法必须接受 CancellationToken 参数,默认值为 default

public interface IPaymentProvider
{
// ① ct = default 让消费者不传也能用,但实现内部必须传播
Task<PaymentResult<PaymentOrderResult>> CreateOrderAsync(
PaymentOrderRequest request, CancellationToken ct = default);
}

实现内部传播

public async Task<PaymentResult<PaymentStatusResult>> QueryStatusAsync(
string outTradeNo, CancellationToken ct = default)
{
// ① 方法入口校验取消(推荐先于参数校验)
ct.ThrowIfCancellationRequested();
// ② 传递给下游 HttpClient 调用
var json = await _httpClient.PostAsync(MethodQuery, bizParams, ct);
// ③ 如果有多个 await,每个都要传 ct
var response = await _httpClient.GetAsync(url, ct);
var content = await response.Content.ReadAsStringAsync(ct);
// ...
}

DI 注册时的传播

注册 HttpClient 时设置超时:

services.AddHttpClient(options.HttpClientName, client =>
{
client.Timeout = options.Timeout; // ① 超时也会触发 CancellationToken
}).AddRequestLogging(options.HttpClientName);

OperationCanceledException 处理

正确做法(新代码)

public async Task<PaymentResult<T>> SomeOperationAsync(CancellationToken ct)
{
try
{
var result = await _httpClient.PostAsync(url, body, ct);
return PaymentResult<T>.Success(result);
}
catch (OperationCanceledException)
{
// ① 取消不是失败,直接重抛让调用方知道
throw;
}
catch (PaymentException ex)
{
return PaymentResult<T>.Fail(ex.ErrorCode, ex.ProviderMessage);
}
catch (HttpRequestException ex)
{
return PaymentResult<T>.Fail("HTTP_ERROR", ex.Message);
}
}

已知问题(旧代码)

部分 Sms 厂商实现(Aliyun/Huawei)的旧代码用兜底 catch (Exception) 会把取消异常吞成失败 Result:

// ❌ 错误做法——吞掉了取消异常
catch (Exception ex)
{
return SMSResult.FromException(ex); // 取消异常被误标记为失败
}

新代码必须避免这种模式。

Rest 层的超时处理

RestClient 把超时映射为 RestException

// RestClient.SendAsync 内部
catch (OperationCanceledException ex) when (!ct.IsCancellationRequested)
{
// ① 如果不是消费者主动取消,说明是超时
throw new RestException(
domain: "Rest",
message: "请求超时",
failureKind: IntegrationFailureKind.Timeout,
isTransient: true,
innerException: ex);
}
catch (OperationCanceledException)
{
// ② 消费者主动取消——正常重抛
throw;
}

RequestLogHandler 显式处理取消异常——记录耗时但不记为业务异常,直接重抛。

相关

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%