VSIX - XmlEditingScope.Complete()上的死锁

TWT*_*TWT 9 c# deadlock visual-studio vsix visual-studio-extensions

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

由于某种原因,在调用XmlEditingScope.Complete()方法后发生死锁.在Visual Studio的状态栏中,我们看到消息"等待解析完成..."

这是死锁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
Run Code Online (Sandbox Code Playgroud)

并且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]  
Run Code Online (Sandbox Code Playgroud)

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

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

       s.Complete();
}
Run Code Online (Sandbox Code Playgroud)

我该怎么做才能防止这种僵局的发生.在应用更改之前,我是否需要锁定某些内容?还有什么我可能做错了?

Evk*_*Evk 0

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

我将用这个小例子(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; }
}
Run Code Online (Sandbox Code Playgroud)

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