CAD二次开发中的线程、异步操作与LockDocument

它触及了CAD二次开发(尤其是AutoCAD .NET API)的核心架构限制。,我将从多个层面为你详细解释。

这里是目录

核心根本原因:CAD对象的 线程关联性(Thread Affinity)

AutoCAD的核心对象(如 DatabaseEntityBlockTable 等)都与创建它们的主UI线程紧密绑定。这是最根本的设计约束。

1. AutoCAD的架构本质

AutoCAD是典型的单线程公寓模型(STA - Single Threaded Apartment) 应用程序:

  • 所有用户界面操作必须在主线程(通常是UI线程)执行
  • 图形数据库访问必须通过主线程
  • 这是COM遗留架构与现代.NET的冲突点

2. 为什么 LockDocument() 是必须的?

csharp 复制代码
using (doc.LockDocument())
{
    // 这里的代码在"文档锁"保护下执行
    // 但仍在主线程上!
}

重要误解澄清LockDocument() 并不改变线程上下文!它只是:

  • 防止其他会话(如网络上的其他用户)同时修改文档
  • 确保当前操作的事务完整性
  • 但它仍在调用它的线程上执行

3. 事务(Transaction)的限制

csharp 复制代码
using (Transaction tr = db.TransactionManager.StartTransaction())
{
    BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
    // 所有tr.GetObject()获取的对象都与此事务关联
    // 事务本身是线程特定的
}

事务对象及其获取的所有数据库对象都绑定到创建事务的线程。如果尝试在其他线程访问这些对象,会抛出异常。

具体技术限制的体现

场景示例:为什么这样会失败

csharp 复制代码
// 错误示例 - 绝对不要这样做!
Task.Run(() => 
{
    using (var doc = Application.DocumentManager.MdiActiveDocument)
    using (doc.LockDocument())
    {
        // 即使加了LockDocument,这里仍在后台线程!
        // 访问数据库会抛出异常:
        // eInvalidInput: "Cannot access the document from a different thread"
        var db = doc.Database;
        // ... 这里会崩溃
    }
});

正确的模式:主线程执行CAD操作,异步处理非CAD工作

csharp 复制代码
// 正确模式:分离CAD操作和非CAD操作
public async void DoWorkAsync()
{
    // 第1步:在主线程同步获取CAD数据
    List<EntityData> cadData;
    using (var doc = Application.DocumentManager.MdiActiveDocument)
    using (doc.LockDocument())
    {
        cadData = ExtractDataFromCAD(doc.Database); // 同步提取
    }
    
    // 第2步:将纯数据(非CAD对象)送到后台处理
    var bomResult = await Task.Run(() => ProcessBOMAsync(cadData));
    
    // 第3步:如果需要写回CAD,回到主线程
    using (var doc = Application.DocumentManager.MdiActiveDocument)
    using (doc.LockDocument())
    {
        WriteResultsToCAD(doc.Database, bomResult); // 同步写回
    }
}

// 这个方法处理纯数据,没有CAD对象
private BOMResult ProcessBOMAsync(List<EntityData> data)
{
    // 这里可以安全地使用异步、并行等
    // 因为EntityData是自定义的DTO,不是CAD对象
    return CalculateBOM(data);
}

更深层次的设计原因

1. 图形数据库的线程安全性

AutoCAD的图形数据库(Database)不是线程安全的:

  • 没有内置的锁机制来处理多线程并发访问
  • 对象ID到实际对象的映射是线程特定的
  • 撤销/重做机制依赖于严格的执行顺序

2. COM遗留问题

AutoCAD API大量基于COM:

  • 许多底层对象是COM对象
  • COM对象通常有线程亲和性要求
  • 跨线程封送(marshaling)CAD对象代价高昂且不稳定

3. 性能与稳定性的权衡

AutoCAD优先考虑:

  • 图形显示的实时性
  • 操作的确定性
  • 内存管理的可预测性

允许异步操作会引入:

  • 竞态条件
  • 死锁风险
  • 内存泄漏(COM对象生命周期问题)

实际开发中的解决方案

模式1:数据提取 + 后台处理 + 主线程更新

csharp 复制代码
public class AsyncCADProcessor
{
    // 提取CAD数据到线程安全的DTO
    public CADSnapshot CaptureSnapshot()
    {
        // 必须在主线程调用
        var snapshot = new CADSnapshot();
        using (var tr = db.TransactionManager.StartTransaction())
        {
            foreach (ObjectId id in modelSpace)
            {
                var ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
                snapshot.Entities.Add(new EntityInfo
                {
                    Handle = ent.Handle.ToString(),
                    Layer = ent.Layer,
                    Bounds = ent.GeometricExtents
                    // 只提取数据,不保留CAD对象引用
                });
            }
        }
        return snapshot;
    }
    
    // 后台处理
    public async Task<AnalysisResult> AnalyzeAsync(CADSnapshot snapshot)
    {
        return await Task.Run(() =>
        {
            // 这里是纯数据处理
            return PerformComplexAnalysis(snapshot);
        });
    }
}

模式2:使用AutoCAD的 Idle 事件进行伪异步

csharp 复制代码
// 利用Application.Idle事件实现协作式多任务
private Queue<Action> _pendingOperations = new Queue<Action>();

public void ScheduleCADOperation(Action operation)
{
    lock (_pendingOperations)
    {
        _pendingOperations.Enqueue(operation);
    }
    
    // 订阅Idle事件(如果尚未订阅)
    Application.Idle += OnApplicationIdle;
}

private void OnApplicationIdle(object sender, EventArgs e)
{
    Action nextOp = null;
    lock (_pendingOperations)
    {
        if (_pendingOperations.Count > 0)
            nextOp = _pendingOperations.Dequeue();
    }
    
    if (nextOp != null)
    {
        nextOp(); // 在主线程执行
    }
    else
    {
        // 没有更多操作,取消订阅
        Application.Idle -= OnApplicationIdle;
    }
}

特别注意事项

哪些可以异步?

  • 文件I/O(读写非DWG文件)
  • HTTP请求(获取BOM数据、调用Web API)
  • 复杂计算(统计分析、几何计算使用纯数学库)
  • 数据库查询(SQL Server等外部数据库)

哪些绝对不能异步?

  • 任何直接访问 Database 的操作
  • 事务内的任何操作
  • 用户界面更新(编辑器、对话框)
  • 与图形显示相关的操作

总结建议

  1. 明确线程边界:设计时清晰区分"CAD线程工作"和"非CAD线程工作"
  2. 使用DTO模式:在主线程提取数据到普通对象,送到后台处理
  3. 考虑使用反应式编程:Rx.NET可以帮助管理异步工作流
  4. 利用并行处理纯数据 :LINQ的 AsParallel() 可用于处理提取出的数据集合
  5. 对于长时间操作:使用模态进度对话框,允许用户取消,但仍在主线程执行CAD操作

记住关键原则:AutoCAD API调用必须在UI线程同步执行,但围绕CAD的数据处理可以异步化。 这种分离架构虽然增加了复杂性,但确保了CAD的稳定性和性能。

相关推荐
游乐码7 小时前
c#万物之父装箱拆箱
开发语言·c#
GIS程序猿8 小时前
批量出图工具,如何使用C#实现动态文本
开发语言·arcgis·c#·arcgis插件·gis二次开发
量子物理学8 小时前
三、C#高级进阶语法——特性(Attribute)
java·算法·c#
量子物理学8 小时前
四、C#高级进阶语法——委托(Delegate)
开发语言·c#
bepeater123410 小时前
Laravel9.X核心特性全面解析
c语言·c++·c#·php
lpfasd12310 小时前
Markdown 导出 Word 文档技术方案
开发语言·c#·word
m5655bj12 小时前
通过 C# 将 PPT 文档转换为 HTML 格式
c#·html·powerpoint
未来之窗软件服务12 小时前
AI人工智能(十五)C# AI的智障行为http服务—东方仙盟练气期
开发语言·http·c#
缺点内向12 小时前
C#中如何创建目录(TOC):使用Spire.Doc for .NET实现Word TOC自动化
c#·自动化·word·.net
2301_8169978812 小时前
Word 创建打开与保存文档
c#·word·xhtml