Вызвать делегата в главном потоке в консольном приложении. - PullRequest
4 голосов
/ 07 сентября 2010

В приложении Windows, когда используются несколько потоков, я знаю, что для обновления компонентов GUI необходимо вызывать основной поток. Как это сделать в консольном приложении?

Например, у меня есть два потока: основной и дополнительный. Вторичный поток всегда слушает глобальную горячую клавишу; когда она нажата, вторичный поток выполняет событие, которое обращается к API-интерфейсу win32 AnimateWindow. Я получаю сообщение об ошибке, потому что только основной поток может выполнять указанную функцию.

Как я могу эффективно указать основному потоку выполнить этот метод, когда «Invoke» недоступен?


обновление: , если это поможет, вот код. Чтобы увидеть материал HotKeyManager (где вступает в игру другой поток), посмотрите ответ на этот вопрос
class Hud
{
    bool isHidden = false;
    int keyId;

    private static IntPtr windowHandle;

    public void Init(string[] args)
    {
        windowHandle = Process.GetCurrentProcess().MainWindowHandle;
        SetupHotkey();
        InitPowershell(args);
        Cleanup();
    }

    private void Cleanup()
    {
        HotKeyManager.UnregisterHotKey(keyId);
    }

    private void SetupHotkey()
    {

        keyId = HotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Control);
        HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
    }

    void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
    {
       ToggleWindow();
    }

    private void ToggleWindow()
    {
        //exception is thrown because a thread other than the one the console was created in is trying to call AnimateWindow

        if (isHidden)
        {
            if (!User32.AnimateWindow(windowHandle, 200, AnimateWindowFlags.AW_VER_NEGATIVE | AnimateWindowFlags.AW_SLIDE))
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        else
        {
            if (!User32.AnimateWindow(windowHandle, 200, AnimateWindowFlags.AW_VER_POSITIVE | AnimateWindowFlags.AW_HIDE))
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        isHidden = !isHidden;
    }

    private void InitPowershell(string[] args)
    {
        var config = RunspaceConfiguration.Create();
        ConsoleShell.Start(config, "", "", args);
    }
}

Ответы [ 3 ]

8 голосов
/ 07 сентября 2010

Как указано в документации на MSDN :

Функция не будет работать в следующих ситуациях:

  • [...]
  • Если поток не является владельцем окна.[...]

Таким образом, здесь нет «основного» обсуждаемого потока (AFAIK Win32Api не заботится о том потоке ведьмы, в котором выполняется точка входа вашей программы).

Единственное условие - вы должны выполнить AnimateWindow в потоке, которому принадлежит окно, которое вы анимируете.Это тот, который вызывал CreateWindow , так как это функция, определяющая бесконечность потока / цикла сообщений).

  • Большую часть времени, как сказал Джон, этот поток должен запускатьцикл сообщений, созданный Application.Run .Из другого потока вы можете использовать метод Control.Invoke , чтобы заставить основной поток выполнить код.Если у вас нет ссылки Control, просто создайте ее в основном потоке и вызовите ее CreateHandle метод.Если у вас есть основная форма, просто используйте ее
  • Цикл сообщений также можно создать по-старому, особенно если вы все равно уже создали свое окно с помощью PInvoke.Основной поток должен иметь стандартный цикл PeekMessage , ожидающий по крайней мере WM_QUIT и WM_EXECUTE_ANIMATE_WINDOW, которые вы определяете.Вторичный поток, представляющий это сообщение через PostMessage или PostThreadMessage.

Теперь, когда вы опубликовали свой пример кода, проблема, с которой вы столкнетесь, заключается в том, что вы не пытаетесь анимировать только любое окно ...но вы пытаетесь анимировать само окно консоли ... И вы не в потоке его владельца (иначе он не обновится, когда вы создадите бесконечный цикл в вашем приложении) ... поэтому вызов AnimateWindow будет невозможенза исключением случаев, когда вам удается принудительно заставить окна выполнять код в этом потоке.

Тот факт, что окна консоли фактически принадлежат CSRSS, ведь системный процесс, выполняющийся с повышенными правами, делает в любом случае очень опасным взаимодействие с ними.

Поскольку Windows Vista даже не может отправить сообщение в такие окна из-за защиты процесса, поэтому любая уязвимость, которая могла быть использована ранее для принудительного выполнения этого потока для выполнения кода, теперь должна быть непригодной.

Для получения подробной информации оСпецифика консольного окна смe Почему консольные окна не предназначены для Windows XP? сообщение в блоге Рэймонда Чена (из команды Microsoft Windows Shell, так что в значительной степени из источника)

0 голосов
/ 07 сентября 2010

Я думал о фоновом работнике, но поскольку у вас нет потока пользовательского интерфейса, о котором не может быть и речи ( см. Этот вопрос )

«Классический» метод потоков с использованием семафоровнаверное работает.Используйте потокобезопасную очередь или коллекцию для хранения событий и уведомления основного потока о том, что есть работа, которую необходимо выполнить через объект синхронизации.

0 голосов
/ 07 сентября 2010

Обычно это , а не в консольном приложении. Если вы пытаетесь использовать Win32 GUI API, вы должны запустить цикл сообщений, я подозреваю.

Вы можете вызвать Application.Run() или Application.Run(ApplicationContext) из своего консольного приложения, чтобы начать новый цикл обработки сообщений. Идея состоит в том, чтобы затем использовать SynchronizationContext.Current, чтобы выполнить маршалинг обратно в основной поток. Однако мне пока не удалось заставить это работать ... вам нужно каким-то образом заставить его зарегистрировать цикл сообщений в качестве текущего контекста синхронизации, и мне не удалось убедить его сделать это: (

...