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

WPF异步事件实现:子UserControl向主窗口异步传数方案咨询

实现WPF子控件与主窗口的全链路异步数据交互

核心问题分析

你当前的实现里,RelayCommand是同步执行的,且事件订阅用了async void(虽然事件处理是async void的合法场景,但全链路异步需要让命令本身支持异步,同时确保所有数据处理逻辑都不阻塞UI线程)。下面给出两种符合异步最佳实践的改造方案:


方案1:基于异步RelayCommand+异步事件

第一步:实现支持异步的RelayCommand

WPF原生或常见MVVM库的RelayCommand通常不支持异步执行,先实现一个异步版本,既保证UI线程不被阻塞,又能防止重复执行:

public class AsyncRelayCommand<T> : ICommand
{
    private readonly Func<T, Task> _execute;
    private readonly Predicate<T> _canExecute;
    private bool _isExecuting;

    public event EventHandler CanExecuteChanged;

    public AsyncRelayCommand(Func<T, Task> execute, Predicate<T> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return !_isExecuting && (_canExecute == null || _canExecute((T)parameter));
    }

    public async void Execute(object parameter)
    {
        if (CanExecute(parameter))
        {
            try
            {
                _isExecuting = true;
                await _execute((T)parameter);
            }
            finally
            {
                _isExecuting = false;
                RaiseCanExecuteChanged();
            }
        }
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

第二步:改造子ViewModel支持异步事件触发

把同步命令改成异步命令,事件委托替换为异步类型,确保触发事件时等待所有异步处理完成:

public class ProjectsViewModel
{
    public AsyncRelayCommand<Project> SelectProjectCommand { get; private set; }
    // 用异步委托替代同步Action
    public event Func<Project, Task> SelectProjectRequested = delegate { };

    public ProjectsViewModel()
    {
        SelectProjectCommand = new AsyncRelayCommand<Project>(SelectProject);
    }

    private async Task SelectProject(Project project)
    {
        var handlers = SelectProjectRequested.GetInvocationList().Cast<Func<Project, Task>>();
        // 遍历所有订阅者并异步执行
        foreach (var handler in handlers)
        {
            await handler(project);
        }
    }
}

第三步:主ViewModel异步订阅事件

将事件处理方法改为async Task(避免async void的异常捕获隐患):

public class MainViewModel
{
    private readonly ProjectsViewModel _projectsViewModel;
    private readonly SearchViewModel _searchViewModel;

    public MainViewModel()
    {
        _projectsViewModel = new ProjectsViewModel();
        _searchViewModel = new SearchViewModel();
        // 订阅异步事件
        _projectsViewModel.SelectProjectRequested += InitializeSearchView;
    }

    private async Task InitializeSearchView(Project project)
    {
        if (project == null) return;
        await _searchViewModel.GetAllMaterialsForProjectAsync(project);
    }
}

方案2:基于异步回调(更简洁的耦合方式)

如果不需要多订阅者,直接给子ViewModel传递异步回调比事件更直接,代码也更简洁:

子ViewModel改造

public class ProjectsViewModel
{
    public AsyncRelayCommand<Project> SelectProjectCommand { get; private set; }
    private readonly Func<Project, Task> _onProjectSelected;

    // 通过构造函数注入异步回调
    public ProjectsViewModel(Func<Project, Task> onProjectSelected)
    {
        _onProjectSelected = onProjectSelected ?? throw new ArgumentNullException(nameof(onProjectSelected));
        SelectProjectCommand = new AsyncRelayCommand<Project>(SelectProject);
    }

    private async Task SelectProject(Project project)
    {
        await _onProjectSelected(project);
    }
}

主ViewModel初始化

public class MainViewModel
{
    private readonly ProjectsViewModel _projectsViewModel;
    private readonly SearchViewModel _searchViewModel;

    public MainViewModel()
    {
        _searchViewModel = new SearchViewModel();
        // 传递异步回调方法
        _projectsViewModel = new ProjectsViewModel(InitializeSearchView);
    }

    private async Task InitializeSearchView(Project project)
    {
        if (project == null) return;
        await _searchViewModel.GetAllMaterialsForProjectAsync(project);
    }
}

关键最佳实践点

  • 避免同步命令阻塞UI线程:用异步RelayCommand封装所有异步逻辑
  • 尽量减少async void的使用:除了ICommand.Execute(因接口限制必须返回void),其他异步方法都返回Task或Task<T>
  • 防止重复执行:在异步命令中维护_isExecuting状态,确保同一时间只有一个异步操作在执行
  • 异常处理:可以在AsyncRelayCommand的try-catch中添加全局异常捕获逻辑,避免未处理的异步异常导致程序崩溃

内容的提问来源于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 02:15:04