WPF视图切换时如何中止API调用?
WPF视图切换时中止API调用的解决方案
我的WPF应用顶部设有导航按钮,点击后可切换不同视图,但切换视图时,之前视图发起的API调用不会自动中止——就算已经切走,旧API返回404、500等错误时,错误弹窗仍会弹出。相关代码如下:
现有代码
XAML
<!-- FEE SURVEY BUTTON --> <Menu:Btn Command="{Binding FeeSurveyCommand}" IsChecked="{Binding NavBar.FeeSurvey}" Style="{StaticResource NavButton}"> </Menu:Btn> <!-- PRACTICE SNAPSHOT BUTTON --> <Menu:Btn Command="{Binding PracticeSnapshotCommand}" IsChecked="{Binding NavBar.PracticeSnapshot}" Style="{StaticResource NavButton}"> </Menu:Btn> <!-- 动态切换的内容区域 --> <ContentControl x:Name="Pages" Grid.Column="1" Margin="25,0" Content="{Binding CurrentView}" />
MainViewModel
private object _currentView; public object CurrentView { get { return _currentView; } set { _currentView = value; OnPropertyChanged(); } } public ICommand FeeSurveyCommand { get; set; } public ICommand PracticeSnapshotCommand { get; set; } private void FeeSurvey(object obj) { CurrentView = new FeeSurveyVM(); } private void PracticeSnapshot(object obj) { CurrentView = new PracticeSnapshotVM(); } // 构造函数 public MainViewVM() { FeeSurveyCommand = new RelayCommand(FeeSurvey); PracticeSnapshotCommand = new RelayCommand(PracticeSnapshot); }
FeeSurveyVM(子ViewModel示例)
// 构造函数 Dictionary<string, string> queryParams = new() { {"startdate", "2023-01-01"}, {"enddate", "2024-01-01"}, }; var res = await ApiCalls.AuthGetApiWithHeader<JObject>("/fee_surveys", queryParams); // 处理API返回数据
ApiCalls.cs
public static async Task<T> AuthGetApiWithHeader<T>(string endpoint, Dictionary<string, string> queryParams = null) { try { using var client = new HttpClient(); // 构建请求URL的逻辑 HttpResponseMessage response = await client.GetAsync(requestUrl); if (response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.NoContent) { return default; } else { T result = await response.Content.ReadAsAsync<T>(); return result; } } else { HandleApiCallErrorsAsync(response); // 错误处理函数 return default; } } catch (Exception) { MessageBoxHelper.ShowCustomMessageBox("error", "ERROR", "An error occurred. Please try again."); return default; } }
解决方案:利用取消令牌中止API请求
核心思路是通过CancellationTokenSource跟踪未完成的API请求,在视图切换时取消旧请求并清理资源。
步骤1:让子ViewModel实现IDisposable接口
所有发起API请求的子ViewModel(如FeeSurveyVM、PracticeSnapshotVM)需持有取消令牌源,并在销毁时取消请求:
public class FeeSurveyVM : INotifyPropertyChanged, IDisposable { private CancellationTokenSource _cts; public FeeSurveyVM() { _cts = new CancellationTokenSource(); _ = LoadDataAsync(); // 异步加载数据,构造函数不允许async } private async Task LoadDataAsync() { try { Dictionary<string, string> queryParams = new() { {"startdate", "2023-01-01"}, {"enddate", "2024-01-01"}, }; // 传入取消令牌 var res = await ApiCalls.AuthGetApiWithHeader<JObject>("/fee_surveys", queryParams, _cts.Token); // 处理返回数据 } catch (OperationCanceledException) { // 请求被取消,无需处理错误 } catch (Exception) { MessageBoxHelper.ShowCustomMessageBox("error", "ERROR", "An error occurred. Please try again."); } } public void Dispose() { _cts?.Cancel(); // 取消未完成的请求 _cts?.Dispose(); // 释放资源 } // INotifyPropertyChanged实现逻辑... }
步骤2:修改ApiCalls方法支持取消令牌
更新API调用方法,接受CancellationToken并传入HttpClient请求,同时跳过取消请求的错误弹窗:
public static async Task<T> AuthGetApiWithHeader<T>(string endpoint, Dictionary<string, string> queryParams = null, CancellationToken cancellationToken = default) { try { using var client = new HttpClient(); // 构建请求URL的逻辑 // 传入取消令牌 HttpResponseMessage response = await client.GetAsync(requestUrl, cancellationToken); if (response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.NoContent) { return default; } else { T result = await response.Content.ReadAsAsync<T>(cancellationToken); return result; } } else { // 仅当请求未被取消时处理错误 if (!cancellationToken.IsCancellationRequested) { HandleApiCallErrorsAsync(response); } return default; } } catch (OperationCanceledException) { // 请求被取消,直接返回默认值,不弹窗 return default; } catch (Exception) { // 仅当请求未被取消时弹出错误 if (!cancellationToken.IsCancellationRequested) { MessageBoxHelper.ShowCustomMessageBox("error", "ERROR", "An error occurred. Please try again."); } return default; } }
步骤3:在MainViewModel切换视图时清理旧资源
修改CurrentView属性的setter,在设置新视图前,销毁旧ViewModel并触发取消:
private object _currentView; public object CurrentView { get { return _currentView; } set { // 清理旧ViewModel资源 if (_currentView is IDisposable disposableOldView) { disposableOldView.Dispose(); } _currentView = value; OnPropertyChanged(); } }
内容的提问来源于stack exchange,提问作者May Nguyen
相关产品推荐
相关产品推荐

