Thursday, December 31, 2015

The Sluggish Three Finger Salute

Not So Shortcuts

Like many of you I recently upgraded both my portable and my primary workstation to Windows 10. Unlike some of you I did this willingly, having heard that this release of Windows was significantly improved from v8.1 (which I did not install) and because I do like to stay on top of the platform for my clients that will be using it.

Shortly after installing the upgrade, and having gone through some painful changes and learning curves, I noticed that my Windows shortcut keys were responding very sluggishly. As a developer and keyboard enthusiast I have quite a number of shortcut keys defined at the OS level. For example Ctrl-Alt-U will pop up my trust copy of UltraEdit, Ctrl-Alt-Q will fire up SQL Server Management Studio, Ctrl-Alt-X for Excel. You get the picture.

I hadn't seen this sluggish shortcut behavior in Windows 7 and it was immensely frustrating. Almost all my applications are on SSD drives and I'm running a quad-core i7 CPU so they should be flying up on the screen! It had to be something OS-specific. So I employed a little Google-Fu to determine the cause of the problem.

My research uncovered a fair amount about how the shortcut key system operates and how Window 8 and 10 Metro apps behave. This question and answer on SuperUser gives a concise synopsis of the root problem, which is that some Windows processes don't behave well, especially newer "Metro" apps. When closing these apps they remain memory-resident and eventually become "tombstoned", which apparently means they are taking up memory but not responding to many Windows messages. I assume they respond to a request to be restarted though.

That SuperUser entry led me to this excellent article from Raymond Chen of Microsoft that indicated how Windows locates the program that should receive the shortcut key. It will first cycle through all existing processes to see if its the owner of the shortcut key. The problem is that some processes do not respond to the Windows message in a timely fashion, causing the delay. How inconsiderate.

Having learned about the cause of the problem the issue became a detective case — how to track down the inconsiderate processes. I started with the suggestions in the SuperUser post with some success but the problem kept reappearing. I don't have time to pore through my Task Manager processes a couple of times a day to track down every ill-behaved program. I needed some way to identify the offending processes quickly.

Being a fan of the "quick-and-dirty" utility I fired up Visual Studio (using a shortcut key) and put together a quick console application that will enumerate the top-level Windows processes and query each for the hot key. The code is shown below.


This is a pretty brute-force approach to the problem but appears to work. I coded it to query the windows in a multi-threaded fashion but in practice that probably wasn't necessary. The response time to the SendMessageTimeout API call is in very small fractions of seconds, frequently sub-millisecond, so the multi-threading probably adds unnecessary overhead. I'm not to keen to refactor it because it works fast enough for me at the moment.

The process calls the EnumWindows API method to loop through all the top level windows that are currently running. Some of these are low-level processes that are not important for our purposes but I didn't want to exclude anything that might be relevant. The EnumWindows method takes as a parameter a callback method that will be executed once for each window located. This is handled using a defined delegate signature that is used with inline method invocation. Within that method I start a thread for a process that will discover the window's associated name and hot key.

The QueryHotKey method will first query the window for its name. The name may not be accessible for a variety of reasons, the most likely of which is accessibility. If not available I just list it as such rather than go to great lengths to determine the process information. In most cases the cause of the shortcut key delay will be something more mundane for which the name is available.

The heart of the utility is the call to SendMessageTimeout API method. This will send a Windows message to the window to query their shortcut key, with the stipulated timeout in milliseconds. My research led me to believe that Windows 8/10 waits three seconds before moving on so I used the same value.

Once the windows list has been exhausted I wait for the threads to complete, then output the results to the console. In practice I ran this from a command line and used good old command line output redirection to spool the results to a file, which I can view in Notepad or any other text editor to find the culprit(s).

Do I Really Need Calc To Stay Resident?

So I ran my code, which worked pretty much correctly on the first pass, a situation definitely unusual for me. I needed some minor additions to the results and got what I needed.

What it uncovered was pretty interesting. The two processes that were causing the problem for me were newer Windows apps, that is the Windows Calculator and the Windows Settings app. I had opened both of them earlier and closed them but they remain resident in a hibernated state that does not respond to some messages, apparently. I will admit that I'm no expert on the structure of these newer apps and their "tombstoned" state. I'll leave that commentary to those more knowledgeable than me.

Once I killed the tasks I tried a shortcut key and sure enough my text editor sprang immediately to life. Hooray! However, the problem reoccurs when I start one of the apps that remains a background task. If you check out Andy Geisler's reply to the SuperUser question he lists some tips for prohibiting this behavior, and this page has a step-by-step tutorial on how to disable various Windows background apps. For me, disabling Store and Settings appears to have been the most effective.

7 comments:

  1. I too have a shortcut that takes ~5 seconds. I've tried to locate Windows Settings or Calc in my processes and sure enough, Settings was there. Closed it; start-up time immediately went to 0s.

    Thank you very much for your work on this!

    ReplyDelete
    Replies
    1. You're very welcome Paul. I ended up coding a small system tray utility to periodically scan the running processes and kill the ones that will cause the shortcut delay. It uses a lot of the code found in this post and keeps me from having to locate the processes manually.

      Delete
  2. As a sidenote: disabling Background Apps via Windows 10 Settings doesn't help. I have *everything* disabled there, but still Calculator, Weather, Settings regularly appear as "Suspended" processes. So irritating...

    ReplyDelete
  3. Levan, you are absolutely correct. I also discovered that disabling background apps in Settings does not help. Thanks, Microsoft, for insisting you know what we need rather thank what we want. Levan, I hope this article helped you.

    ReplyDelete
  4. I just found this old blog post whilst looking for the offending process on my laptop. I added this to the DllImports:
    [DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

    and modified the window message (line 99) to this:
    uint pid;
    GetWindowThreadProcessId(hWnd, out pid);
    message.AppendFormat("Window [{0} pid:{1}], hotkey ", builder.ToString(), pid);

    This gives me the process ID of the bad process which gives me more ability to figure out what it is.

    Thank you very much for publishing this and saving me the need to duplicate your effort!

    ReplyDelete
  5. Thank you so much dude. I've been using my own dirty hot key manager for decades now for this exact reason: MS had this sluggishness since windows 2000 or maybe even before that? back when metro was just a subway. Problem with my hack was it would open as many copies of a document as many times I pressed the key combination. Eventually I lost some important notes I've been typing into the notepad because there were 2 copies of them open. With little hope to find a solution I decided to give it a try and I found this. Such a life saver. After adding Process.Kill() and some other whistles it works perfectly.

    ReplyDelete
  6. Great article and source code. This page is still relevant for today and the code compiled inside a new VS 2022 blank console app project the first try. I only made some small changes and added the fetch PID code recommended above. This small utility helped me identify processes that were delaying desktop icon shortcut key actions. But there is still no ACTUAL FIX from Micro$$oft. They are too busy playing around with pastel colors and changing the UI all around on every product for no reason. Thank you.

    ReplyDelete