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

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()保证窗口模态且交互正常

关键说明

  1. AutoCAD线程限制:所有调用AutoCAD API的代码必须在主线程执行,不能用Task.Run开启后台线程。await Task.Yield()可以让WPF在执行耗时操作的间隙处理界面消息,保证窗口可交互。
  2. 通用委托设计:Func<IProgress<int>, CancellationToken, Task>支持传递进度通知和取消令牌,兼容任意符合签名的耗时方法,实现通用调用。
  3. 进度与取消:通过IProgress<T>实现进度更新,CancellationTokenSource支持用户主动取消操作,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:52:40