Visual Studio 2015 在打字或在撕掉的编辑器之间移动时挂起

Visual Studio 2015 hangs while typing or moving between torn-off-editors

当我在使用 Visual Studio 2015 时输入 C# 文件时,它会间歇性地一次挂起长达 30 秒。这种情况是随机发生的,没有任何明确的原因(没有高 CPU,或者点击按钮后没有)。

当多个编辑器被撕掉后,windows之间的点击也需要很长时间。

一时兴起,我推测问题可能与 DDE / SendMessage(HWND_BROADCAST, …) 有关。快速测试程序确认发送到 HWND_BROADCAST 的消息将挂起呼叫者。

为了找出 window 的罪魁祸首,我在下面编写了测试程序 - 在我的例子中,它指向了 OfficeC2RClient。终止 OfficeC2RClient 进程解决了问题。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SendMessageTest
{
    class Program
    {
        private static readonly IntPtr
            HWND_BROADCAST = (IntPtr)65535;
        private const int
            WM_NULL = 0;

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, int fuFlags, int uTimeout, out IntPtr result);

        protected delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        protected static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        protected static extern int GetWindowTextLength(IntPtr hWnd);

        [DllImport("user32.dll")]
        protected static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);

        [DllImport("user32.dll")]
        protected static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true)]
        static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int processId);

        protected static bool EnumTheWindows(IntPtr hWnd, IntPtr lParam)
        {
            int size = GetWindowTextLength(hWnd);
            if (size++ > 0 && IsWindowVisible(hWnd))
            {
                StringBuilder sb = new StringBuilder(size);
                GetWindowText(hWnd, sb, size);
                Console.WriteLine(sb.ToString());
            }
            return true;
        }

        static void Main(string[] args)
        {
            LoopSendMessage();
        }

        private static void BroadcastMessage()
        {
            while (true)
            {
                var stp = Stopwatch.StartNew();
                SendMessage(HWND_BROADCAST, WM_NULL, IntPtr.Zero, IntPtr.Zero);
                stp.Stop();
                Console.WriteLine("Duration = {0}", stp.Elapsed);

                if (stp.ElapsedMilliseconds > 250)
                {
                    Debugger.Break();
                }

                Thread.Sleep(100);
            }
        }

        private static void LoopSendMessage()
        {
            if (EnumWindows(LoopSendMessage_Call, IntPtr.Zero))
            {
                Console.WriteLine("Completed");
            }
            else
            {
                Console.WriteLine("Failed");
            }
            Console.ReadLine();
        }

        private static bool LoopSendMessage_Call(IntPtr hWnd, IntPtr lParam)
        {
            IntPtr _unused;
            var stp = Stopwatch.StartNew();
            if (SendMessageTimeout(hWnd, WM_NULL, IntPtr.Zero, IntPtr.Zero, 1, 5000, out _unused) == IntPtr.Zero)
            {
                if (stp.ElapsedMilliseconds > 5)
                {
                    if (Marshal.GetLastWin32Error() == 1460)
                    {
                        Console.WriteLine($"HWND {hWnd.ToString("x8")} timed out after {stp.ElapsedMilliseconds:n0}ms");
                    }
                    else
                    {
                        Console.WriteLine($"HWND {hWnd.ToString("x8")} failed after {stp.ElapsedMilliseconds:n0}ms");
                    }

                    int pid;
                    GetWindowThreadProcessId(hWnd, out pid);
                    var proc = Process.GetProcessById(pid);
                    Console.WriteLine($"    {proc.Id} {proc.ProcessName}");
                }
            }
            else if (stp.ElapsedMilliseconds > 250)
            {
                Console.WriteLine($"HWND {hWnd.ToString("x8")} took {stp.ElapsedMilliseconds:n0}ms");
            }
            return true;
        }
    }
}