Проблема в том, как работает GetAsyncKeyState. Он делает больше, чем просто проверяет, нажата ли клавиша в данный момент, а также проверяет, была ли нажата клавиша с момента последнего вызова GetAsyncKeyState. Таким образом, ваша проблема «всегда слушать».
Вы можете использовать GetKeyState, но, честно говоря, я тоже не фанат этой опции. Ваш код должен быть хорошо продуман, чтобы избежать настолько маленького окна опроса, чтобы буквально удерживать клавишу нажатой, чтобы не пропустить ее.
Жизнеспособной альтернативой является маскировка клавиш, когда вы используете комбинацию клавиш, например Shift-Escape. На сайте Чип Пирсон есть достойное введение в эту (и многие другие темы). Тем не менее, это все еще не мой предпочтительный метод.
Мои предпочтения уже упоминались в комментариях. Ваше приложение может быть улучшено с помощью формы пользователя. Это также дает вам возможность получать информацию перед пользователем. Возможно, они не будут пытаться выйти из приложения, если индикатор выполнения в пользовательской форме будет заполнен на 95%. Возможно, вы можете добавить кнопку паузы, которая освобождает некоторые ресурсы для предсказуемой необходимости, а затем возобновляет работу, когда пользователь готов. Этого небольшого дополнительного функционала достаточно, чтобы завоевать меня, но есть еще более веская причина для использования формы пользователя - она делает именно то, что вы просили!
Пользовательские формы и многие пользовательские элементы управления формами имеют события Keydown, Keyup и Keypress, которые срабатывают только тогда, когда форма (или элемент управления) находится в фокусе. Это именно то, что вы хотели.