【问题标题】:VSIX - Deadlock on XmlEditingScope.Complete()VSIX - XmlEditingScope.Complete() 上的死锁
【发布时间】:2016-07-29 08:56:44
【问题描述】:

我们正在使用 Microsoft.VisualStudio.XmlEditor 命名空间 (https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.xmleditor.aspx) 中的类来修改 Visual Studio 扩展中的 xml 文档。

由于某种原因,调用XmlEditingScope.Complete() 方法后发生死锁。在 Visual Studio 的状态栏中,我们看到消息“Waiting for parse to complete...”

这是死锁的 UI 线程的堆栈跟踪:

WindowsBase.dll!System.Windows.Threading.DispatcherSynchronizationContext.Wait(System.IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)   
mscorlib.dll!System.Threading.SynchronizationContext.InvokeWaitMethodHelper(System.Threading.SynchronizationContext syncContext, System.IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)     
[Native to Managed Transition]   
[Managed to Native Transition]   
mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext)   
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext)  
Microsoft.VisualStudio.Package.LanguageService.14.0.dll!Microsoft.VisualStudio.Package.LanguageService.ParseWaitHandle.WaitOne(int millisecondsTimeout, bool exitContext)    
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlLanguageService.WaitForParse(System.IAsyncResult result, Microsoft.XmlEditor.StatusBarIndicator indicator)    
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlLanguageService.WaitForParse()    
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlParserLock.XmlParserLock(Microsoft.XmlEditor.XmlLanguageService service)  
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.Transaction.PushToEditorTreeAndBuffer()  
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.Transaction.Complete()   
XmlEditingScope.Complete() Line 64

Visual Studio 解析线程:

mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x21 bytes  
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext) + 0x28 bytes     
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.LockManager.Lock(object resource, Microsoft.XmlEditor.LockMode mode, Microsoft.XmlEditor.Transaction txId) + 0x14c bytes     
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.TransactionManager.BeginParseSourceTransaction(Microsoft.XmlEditor.XmlSource src, Microsoft.XmlEditor.Transaction parent) + 0x9f bytes   
Microsoft.XmlEditor.dll!Microsoft.XmlEditor.XmlLanguageService.ParseSource(Microsoft.VisualStudio.Package.ParseRequest req) + 0x17d bytes    
Microsoft.VisualStudio.Package.LanguageService.14.0.dll!Microsoft.VisualStudio.Package.LanguageService.ParseRequest(Microsoft.VisualStudio.Package.ParseRequest req) + 0x75 bytes    
Microsoft.VisualStudio.Package.LanguageService.14.0.dll!Microsoft.VisualStudio.Package.LanguageService.ParseThread() + 0x140 bytes   
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x70 bytes    
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0xa7 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x16 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x41 bytes     
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes    
[Native to Managed Transition]  

在这里展示所有相关代码并不容易,但基本上它只是在 WPF DataGrid 控件(ViewModel 中的 IEditableObject.EndEdit)发生更改后执行的以下代码:

using (var s = store.BeginEditingScope("Test", null))
{
       apply changes in xmlModel.Document... 

       s.Complete();
}

我能做些什么来防止这种死锁的发生。在应用更改之前我需要锁定某些东西吗?我还能做错什么?

【问题讨论】:

    标签: c# visual-studio deadlock visual-studio-extensions vsix


    【解决方案1】:

    更多的是评论,但不适合评论字段。很难说出在您的情况下发生这种情况的确切原因,或者仅使用您提供的信息提供一种解决方法(为了提供更多帮助,我们需要最小的示例,我们可以运行并查看问题)。但是,堆栈跟踪表明这是常规的 UI 死锁,由于它们的“单线程”性质(所有\大多数 UI 元素的操作必须在单线程上发生),几乎所有 UI 框架都经常发生这种死锁。线程 A(在这种情况下为 Visual Studio 解析线程)从后台线程向 UI 线程队列发布一个任务并等待它完成(例如 WPF Dispatcher.Invoke 调用就是这样做的)。没有必要在 UI 线程上执行整个任务以发生死锁,只需其中一部分(例如 - 从 UI 控件获取实际 xml)就足够了。然后你在 UI 线程本身上做同样的事情。那就是你 wait 在 UI 线程中的某个等待句柄上(在 UI 线程上使用 lock 语句属于同一类别)。这是非常危险的,并且在这种情况下会导致您(可能)陷入僵局。

    我将用这个小例子 (WPF) 来说明我的观点:

    public partial class MainWindow : Window {
        private DummyXmlParser _test = new DummyXmlParser();
        public MainWindow() {
            InitializeComponent();
            new Thread(() => {
                _test.StartParseInBackground();
                _test.WaitHandle.WaitOne();
            }) {
                IsBackground = true
            }.Start();
    
            _test.StartParseInBackground();
            // don't do this, will deadlock
            _test.WaitHandle.WaitOne();
        }
    }
    
    public class DummyXmlParser {
        public DummyXmlParser() {
            WaitHandle = new ManualResetEvent(false);
        }
    
        public void StartParseInBackground() {
            Task.Run(() => {
                Thread.Sleep(1000);
                // this gets dispatched to UI thread, but UI thread is blocked by waiting on WaitHandle - deadlock
                Application.Current.Dispatcher.Invoke(() =>
                {
                    Application.Current.MainWindow.Title = "Running at UI";
                });
                WaitHandle.Set();
            });
        }
    
        public ManualResetEvent WaitHandle { get; private set; }
    }
    

    在您的情况下,似乎 XmlEditingScope.Complete 在 UI 线程上运行并等待 ParseWaitHandle,这只是您应该避免的行为。要解决此问题,您可以尝试避免在 UI 线程上执行上面的代码,而是在后台线程上运行。

    【讨论】:

    • Evk,感谢您的回答。我曾尝试在后台线程上运行 XmlEditingScope,但随后出现 System.AccessViolationException。在 Microsoft.VisualStudio.Shell.Interop.IVsQueryEditQuerySave2.QueryEditFiles(UInt32 rgfQueryEdit, Int32 cFiles, String[] rgpszMkDocuments, UInt32[] rgrgf, VSQEQS_FILE_ATTRIBUTE_DATA[] rgFileInfo, UInt32& pfEditVerdict, UInt32& prgfMoreInfo) 在 Microsoft.XmlEditor.Transaction.CanEdit在 Microsoft.XmlEditor.Transaction.PushToEditorTreeAndBuffer() 在 Microsoft.XmlEditor.Transaction.Complete()
    • 如果没有更多信息,很难给出更多建议。但至少它现在没有死锁 :) 如果你将它分派到 UI 线程但通过 Dispatcher.BeginInvoke (如此异步)呢?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-16
    相关资源
    最近更新 更多