You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中Action<T>事件触发异步HTTP请求的任务取消问题排查

解决ViewModel间异步触发HTTP请求时的任务取消问题

问题根源分析

你遇到的"任务已取消"异常,核心原因在于同步事件体系与异步方法的不兼容,具体两点:

  1. async void的滥用:事件处理方法InitializeSearchView使用async void,除原生UI事件外,这种写法会导致异步任务的上下文管理失控——异常无法正常冒泡、任务完成状态无法被跟踪,极容易引发线程死锁或任务被提前终止。
  2. 同步事件无法传递异步状态:原代码用Action<Project>作为事件类型,它是无返回值的同步委托,无法让事件触发者等待异步处理完成,可能导致UI上下文被提前释放,进而卡住HttpClient的请求流程。

可行解决方案(无需MVVM Toolkit)

方案一:改用异步事件体系(推荐)

将同步事件替换为支持异步的委托类型,从命令触发到事件处理全程遵循异步原则,彻底避免上下文冲突。

1. 修改ProjectsViewModel(事件发起方)

把Action<Project>事件改为Func<Project, Task>,同时使用异步命令(如Prism的AsyncRelayCommand,或自定义支持异步的RelayCommand):

public class ProjectsViewModel : BindableBase
{
    public ICommand SelectProjectCommand { get; private set; }
    // 替换为异步委托类型,支持返回Task
    public event Func<Project, Task> SelectProjectRequested = delegate { return Task.CompletedTask; };

    public ProjectsViewModel()
    {
        // 使用AsyncRelayCommand处理异步逻辑(若使用Prism);自定义RelayCommand需确保支持异步委托
        SelectProjectCommand = new AsyncRelayCommand<Project>(SelectProject);
    }

    private async Task SelectProject(Project project)
    {
        // 触发异步事件并等待所有处理完成
        await SelectProjectRequested(project);
    }
}

2. 修改主ViewModel(事件处理方)

将事件处理方法改为async Task,去掉async void:

public class ViewModel
{
    public ViewModel()
    {
        _projectsViewModel.SelectProjectRequested += InitializeSearchView;            
    }

    // 改为async Task,让异步状态可跟踪
    private async Task InitializeSearchView(Project project)
    {
       if (project != null)
       {
            await SearchViewModel.GetAllMaterialsForProjectAsync(project);
       }
    }
}

方案二:应急修复async void写法(不推荐,仅适配旧代码)

如果无法修改事件类型,可通过调整异步上下文避免死锁,但需额外处理UI线程切换:

1. 修改主ViewModel的事件处理方法

private async void InitializeSearchView(Project project)
{
    try
    {
        if (project != null)
        {
            // 使用ConfigureAwait(false)避免捕获UI上下文,防止死锁
            await SearchViewModel.GetAllMaterialsForProjectAsync(project).ConfigureAwait(false);
        }
    }
    catch (Exception ex)
    {
        // async void的异常不会自动冒泡,需手动捕获并切回UI线程显示
        Application.Current.Dispatcher.Invoke(() => MessageBox.Show(ex.Message));
    }
}

2. 修改SearchViewModel的HTTP请求逻辑

同样添加ConfigureAwait(false),并在更新UI时切回主线程:

public async Task GetAllMaterialsForProjectAsync(Project project)
{
    HttpRequestMessage msg = new HttpRequestMessage
    {
        Method = HttpMethod.Get,
        RequestUri = new Uri(HttpClientInstance.BaseUri + "materials/get-materials-for-project/")
    };
    msg.Headers.Add("projectID", $"{project.Id}");
    string responseString = null;
    try
    {
        // 跳过UI上下文捕获,避免死锁
        HttpResponseMessage responseMessage = await HttpClientInstance.httpClient.SendAsync(msg).ConfigureAwait(false);
        responseMessage.EnsureSuccessStatusCode();
        responseString = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        // 切回UI线程显示错误
        Application.Current.Dispatcher.Invoke(() => MessageBox.Show(ex.Message));
    }
    if (responseString != null)
    {
        List<WindowEntity> windows = DeserializeMaterials(responseString);
        // 切回UI线程更新ObservableCollection(UI绑定属性必须在主线程修改)
        Application.Current.Dispatcher.Invoke(() => 
        {
            Windows = new ObservableCollection<WindowEntity>(windows);
        });
    }                
}

额外注意事项

  • 确保HttpClientInstance是单例且未被提前释放,HttpClient的实例复用是最佳实践,频繁创建可能导致连接耗尽。
  • 检查请求URI是否正确,网络环境是否正常,这些外部因素也可能导致"任务已取消"异常。

内容的提问来源于stack exchange,提问作者Ryan Daley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 03:05:24