WPF窗口结合AutoCAD API实现通用异步任务传参方案问询
问题
我有一个采用MVVM模式的通用WPF窗口(封装在DLL中),实例化时接收ViewModel对象,用于在AutoCAD API环境下执行长耗时操作时向用户展示状态。现在需要实现通用方式,让窗口的StartTask方法能调用其他类的任意方法,且逻辑要在ViewModel中实现。
当前用异步StartTask方法试图保证界面响应,但直接启动任务再显示窗口会导致界面无响应——因为AutoCAD API不支持多线程。期望窗口能正常显示进度、支持交互(移动窗口、点击按钮)。
现有代码示例:
public partial class BusyIndicatorView : Window { internal BusyIndicatorViewModel BusyIndicatorViewModel { get; set; } public BusyIndicatorView(BusyIndicatorViewModel busyIndicatorViewModel) { InitializeComponent(); BusyIndicatorViewModel = busyIndicatorViewModel; StartTask(); } private async void StartTask() { try { while (true) { // 需在此执行通用方法 } Close(); } catch (Exception ex) { MessageBox.Show("Error"); } } }
以下实现方式不可用(会导致界面无响应):
var heavyTask = new HeavyTask(); var window = new Window(); heavyTask.Start(); window.Show();
解决方案
核心思路
因为AutoCAD API必须在**主线程(UI线程)**执行,不能用多线程异步任务,所以要采用「UI线程内异步让出控制权」的方式:用await Task.Yield()配合进度通知,既执行耗时操作,又让WPF界面有机会刷新和响应交互。同时通过在ViewModel中定义通用委托,实现对任意外部耗时方法的调用支持。
步骤1:改造ViewModel,添加通用委托和进度支持
using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; public class BusyIndicatorViewModel : INotifyPropertyChanged { // 通用委托:支持带进度通知和取消令牌的耗时操作 public Func<IProgress<int>, CancellationToken, Task> LongRunningOperation { get; set; } private int _progressValue; public int ProgressValue { get => _progressValue; set { _progressValue = value; OnPropertyChanged(); } } private string _statusText; public string StatusText { get => _statusText; set { _statusText = value; OnPropertyChanged(); } } // 取消令牌:支持用户点击取消按钮终止操作 private CancellationTokenSource _cts; public CancellationTokenSource Cts { get => _cts ??= new CancellationTokenSource(); set => _cts = value; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:改造View的StartTask方法,绑定ViewModel逻辑
public partial class BusyIndicatorView : Window { private BusyIndicatorViewModel _viewModel; public BusyIndicatorView(BusyIndicatorViewModel viewModel) { InitializeComponent(); _viewModel = viewModel; DataContext = _viewModel; // 绑定DataContext,支持WPF控件绑定进度/状态 StartTask(); } private async void StartTask() { try { if (_viewModel.LongRunningOperation == null) throw new InvalidOperationException("未指定要执行的耗时操作"); // 创建进度通知对象,更新ViewModel的ProgressValue var progress = new Progress<int>(value => { _viewModel.ProgressValue = value; }); // 执行耗时操作:await会让出UI线程控制权,让界面可交互 await _viewModel.LongRunningOperation(progress, _viewModel.Cts.Token); // 操作完成后关闭窗口 Close(); } catch (OperationCanceledException) { MessageBox.Show("操作已取消"); Close(); } catch (Exception ex) { MessageBox.Show($"执行出错:{ex.Message}"); Close(); } } // 示例:窗口的取消按钮点击事件 private void CancelButton_Click(object sender, RoutedEventArgs e) { _viewModel.Cts?.Cancel(); } }
步骤3:调用示例(外部类使用窗口)
// 定义任意耗时操作类 public class AutoCadHeavyTask { // 模拟AutoCAD API的长耗时操作,支持进度和取消 public async Task RunCadOperation(IProgress<int> progress, CancellationToken token) { for (int i = 0; i <= 100; i++) { token.ThrowIfCancellationRequested(); // 这里执行实际的AutoCAD API操作(必须在UI线程) // 例如:Editor.WriteMessage($"执行进度:{i}%\n"); progress.Report(i); // 用Task.Yield()让出UI线程,让界面刷新 await Task.Yield(); // 如果操作本身极快,可加短暂延迟(根据实际情况调整) // await Task.Delay(50, token); } } } // 使用窗口的代码 var task = new AutoCadHeavyTask(); var vm = new BusyIndicatorViewModel { LongRunningOperation = task.RunCadOperation, StatusText = "正在处理AutoCAD任务..." }; var window = new BusyIndicatorView(vm); window.ShowDialog(); // 用ShowDialog()保证窗口模态且交互正常
关键说明
- AutoCAD线程限制:所有调用AutoCAD API的代码必须在主线程执行,不能用
Task.Run开启后台线程。await Task.Yield()可以让WPF在执行耗时操作的间隙处理界面消息,保证窗口可交互。 - 通用委托设计:
Func<IProgress<int>, CancellationToken, Task>支持传递进度通知和取消令牌,兼容任意符合签名的耗时方法,实现通用调用。 - 进度与取消:通过
IProgress<T>实现进度更新,CancellationTokenSource支持用户主动取消操作,提升用户体验。
内容的提问来源于stack exchange,提问作者justSomeUser4
相关产品推荐
相关产品推荐

