首次访问EnvDTE.DTE.Documents抛异常 二次访问正常问题咨询
我尝试获取Visual Studio环境中已打开的文档集合,相关代码在多数解决方案下可正常运行,但在某一个特定解决方案中每次调用都会抛出异常。我曾尝试等待环境完成初始化,但等待任意时长后问题依旧。只要捕获该异常后再次调用该属性,即可正常返回结果。
出问题的代码如下:
internal static IList<Document> GetOpenedDocuments(DTE service) { ThreadHelper.ThrowIfNotOnUIThread(); if (service == null) { throw new ArgumentNullException(nameof(service)); } List<Document> openedDocuments = new List<Document>(); foreach (Document document in service.Documents) { if (document != null) { openedDocuments.Add(document); } } return openedDocuments; }
首次访问service.Documents时总会触发崩溃,堆栈跟踪顶部信息如下:
at EnvDTE.Documents.GetEnumerator()
at XXX.FileHelper.GetOpenedDocuments(DTE service) in
异常信息:
系统找不到指定的文件。(Exception from HRESULT: 0x80070002)
这个0x80070002文件找不到错误是Visual Studio DTE COM接口存在多年的已知问题,触发场景是出问题的解决方案的Documents集合里残留了无效脏条目:通常是上一次关闭解决方案时处于打开状态的文件,后续被删除、移动或者重命名,VS启动加载解决方案时没有把这个失效条目从内存集合中清理掉。
第一次枚举service.Documents时,底层COM组件会逐个加载所有文档的元数据,碰到这个不存在的文件条目就会直接抛出异常。而抛异常的过程中,COM层会自动把这个无效条目从集合里移除,所以第二次再调用枚举时,集合里已经没有脏数据,就能正常返回结果。
等待环境初始化没有任何作用,因为这个问题和初始化流程无关,纯粹是集合内存在无效数据导致的。
直接在枚举逻辑上加针对性的异常捕获和重试即可,最多重试2次就可以覆盖这个场景,不需要加额外等待延迟,参考实现如下:
internal static IList<Document> GetOpenedDocuments(DTE service) { ThreadHelper.ThrowIfNotOnUIThread(); if (service == null) { throw new ArgumentNullException(nameof(service)); } List<Document> openedDocuments = new List<Document>(); int retryCount = 0; // 最多重试2次,覆盖COM层自动清理无效条目的场景 while (retryCount < 2) { openedDocuments.Clear(); try { foreach (Document document in service.Documents) { if (document != null) { openedDocuments.Add(document); } } // 枚举成功直接跳出循环 break; } catch (System.IO.FileNotFoundException) { retryCount++; // 捕获到目标异常后直接重试,第一次异常抛出时COM已经完成无效条目清理 } } if (retryCount >= 2) { // 两次重试都失败才抛出异常,避免吞掉其他非预期错误 throw new InvalidOperationException("枚举已打开文档集合失败"); } return openedDocuments; }
注意事项:
- 这个问题从VS2010到最新版本都偶有出现,没有可以从根源规避的公开API,重试是VS扩展开发社区公认的标准处理方式
- 不要捕获所有类型的异常,只处理对应
0x80070002的FileNotFoundException即可,避免掩盖其他真实的代码错误 - 不需要在重试之间加延迟,异常抛出的瞬间COM层已经完成了无效条目的清理,立刻重试即可
内容的提问来源于stack exchange,提问作者Shay

