6.1 KiB
异步加载使用指南
本文说明应用和 Control 作者如何使用 AtomUI 的异步加载能力。状态模型、协调器时序、生命周期和兼容性由 异步加载架构 定义。
选择加载模式
| 场景 | 协调器 | 典型 Control |
|---|---|---|
| 输入变化触发搜索,后一次请求覆盖前一次 | AsyncSearchLoadCoordinator<TContext, TResult> |
AutoComplete、Mentions、Select |
| 展开节点触发加载,同一节点复用在飞任务 | AsyncExpandLoadCoordinator<TContext, TResult> |
Cascader、TreeView、TreeSelect |
搜索类可以配置 debounce;展开类不使用 debounce,通过 in-flight 去重避免同一节点并发重入。
配置现有 Control
搜索类 Control 通常提供:
| 属性 | 作用 |
|---|---|
AsyncLoadDebounce |
输入停止后等待多久再发送请求;Zero 表示立即发送 |
AsyncLoadTimeout |
单次调用的总超时,包括 debounce 等待 |
OptionsAsyncLoader / OptionsLoader |
实际加载器;null 表示不启用异步加载 |
Select 的加载触发点是 Popup 打开,因此没有 AsyncLoadDebounce。展开类 Control 通常提供
AsyncLoadTimeout 和 DataLoader。
Select
mySelect.OptionsLoader = new MyAsyncLoader();
mySelect.AsyncLoadTimeout = TimeSpan.FromSeconds(5);
public sealed class MyAsyncLoader : ISelectOptionsAsyncLoader
{
public async Task<SelectOptionsLoadResult> LoadAsync(
object? context,
CancellationToken cancellationToken)
{
var items = await _api.FetchUsersAsync(cancellationToken);
return new SelectOptionsLoadResult { Data = items };
}
}
AutoComplete debounce
<atom:AutoComplete OptionsAsyncLoader="{Binding Loader}"
AsyncLoadDebounce="0:0:0.3"
AsyncLoadTimeout="0:0:8" />
新的输入会接管 loading 状态。被覆盖的旧请求返回 Skipped,不会触发终态事件,也不能用旧结果更新 UI。
TreeView 节点加载
myTree.DataLoader = new MyNodeLoader();
myTree.AsyncLoadTimeout = TimeSpan.FromSeconds(30);
public sealed class MyNodeLoader : ITreeItemNodeLoader
{
public async Task<TreeItemLoadResult> LoadAsync(
ITreeItemNode parent,
CancellationToken cancellationToken)
{
var children = await _api.FetchChildrenAsync(parent.Id, cancellationToken);
return new TreeItemLoadResult
{
Data = children,
StatusCode = RpcStatusCode.Success
};
}
}
同一节点的连续展开调用复用一个在飞 Task。首次成功后 item 标记为 AsyncLoaded,由 Control 决定后续是否需要再次加载。
处理终态
Control 的加载结果事件统一暴露 IsSuccess、StatusCode、UserFriendlyMessage 和可选 Data。应用应基于公开结果
类型处理提示,不依赖协调器的内部 CancellationToken 组合方式。
myCascader.ItemAsyncLoaded += (_, args) =>
{
if (args.Result?.IsSuccess == true)
{
return;
}
switch (args.Result?.StatusCode)
{
case RpcStatusCode.Timeout:
ShowToast("加载超时,请重试");
break;
case RpcStatusCode.NetworkFailure:
ShowToast("网络异常");
break;
default:
ShowToast(args.Result?.UserFriendlyMessage ?? "加载失败");
break;
}
};
Loader 必须尊重传入的 CancellationToken。不要捕获并吞掉 OperationCanceledException;协调器需要根据 token 来源
区分 timeout、外部取消和请求覆盖。
新搜索类 Control 接入
- Control 拥有一个长期复用的
AsyncSearchLoadCoordinator<TContext, TResult>。 - 调用前把公开的 debounce 和 timeout 属性同步给协调器。
Skipped直接返回,不清理由新请求接管的 loading 状态,不触发事件。- Success 更新数据;TimedOut、Cancelled 和 Faulted 映射到 Control 的公开结果类型。
- template reapply、detach 或 owner 变化不能遗留旧事件、binding 或请求状态。
private readonly AsyncSearchLoadCoordinator<string?, MyLoadResult> _coordinator = new();
private async Task LoadAsync(string? query)
{
if (Loader is null)
{
return;
}
_coordinator.DebounceInterval = AsyncLoadDebounce;
_coordinator.Timeout = AsyncLoadTimeout;
var outcome = await _coordinator.LoadAsync(
query,
(context, token) => Loader.LoadAsync(context, token));
if (outcome.IsSkipped)
{
return;
}
ApplyOutcome(outcome);
}
新展开类 Control 接入
- 使用
AsyncExpandLoadCoordinator<TContext, TResult>。 - 节点对象默认使用
ReferenceEqualityComparer<T>.Instance;只有稳定业务 ID 明确构成节点身份时才使用自定义 comparer。 - 多个不同节点可以并发加载,同一节点复用在飞 Task。
- 集合和 item 状态更新回到 UI 线程。
- Control detach 时调用
CancelAll()。
private readonly AsyncExpandLoadCoordinator<IMyNode, MyLoadResult> _coordinator =
new(ReferenceEqualityComparer<IMyNode>.Instance);
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_coordinator.CancelAll();
base.OnDetachedFromVisualTree(e);
}
常见问题
Debounce 是否计入 timeout?
计入。Timeout 覆盖从 LoadAsync 调用开始到产生终态的总时间。
超时后 loader 是否继续运行?
协调器会取消传给 loader 的 token。实际底层操作必须正确消费该 token 才能及时停止。
为什么 Skipped 不触发错误或完成事件?
它表示旧搜索已经被新搜索取代。对外发布旧终态可能覆盖新结果或产生错误提示。
源码入口
src/AtomUI.Controls.Shared/AsyncLoad/src/AtomUI.Desktop.Controls/AutoComplete/AbstractAutoComplete.cssrc/AtomUI.Desktop.Controls/Select/Select.AsyncOptionsLoad.cssrc/AtomUI.Desktop.Controls/Cascader/CascaderView.AsyncItemDataLoad.cssrc/AtomUI.Desktop.Controls/TreeView/TreeView.AsyncItemDataLoad.cs