在控制台应用程序中的主线程上调用委托

时间:2010-09-07 06:33:17

标签: c# multithreading winapi console-application

在Windows应用程序中,当使用多个线程时,我知道有必要调用主线程来更新GUI组件。如何在控制台应用程序中完成?

例如,我有两个线程,一个主线程和一个辅助线程。辅助线程始终在侦听全局热键;当它被按下时,辅助线程执行一个事件,该事件延伸到win32 api方法AnimateWindow。我收到一个错误,因为只允许主线程执行所述函数。

当“Invoke”不可用时,如何有效地告诉主线程执行该方法?

<小时/> 更新:如果有帮助,这是代码。要查看HotKeyManager内容(其他线程正在发挥作用),请查看this question

的答案

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 个答案:

答案 0 :(得分:8)

正如documentation on MSDN所说:

  

在以下情况下,该功能将失败:

     
      
  • [...]
  •   
  • 如果线程没有窗口。 [...]
  •   

所以这里没有问题“主要”线程(AFAIK Win32Api不关心执行程序入口点的女巫线程)。

唯一的条件是您必须在拥有您正在设置动画的窗口的线程上执行AnimateWindow。那是调用CreateWindow的那个,因为它是定义线程/消息循环afinity的函数。)

  • 大多数时候,Jon说这个线程应该运行由Application.Run创建的消息循环。
    从另一个线程,您可以使用Control.Invoke方法强制主线程执行码。如果您没有Control引用,只需在主线程中创建一个,并调用它的CreateHandle方法。如果您有一个主表单,只需使用它
  • 消息循环也可以用旧学校的方式创建,特别是如果你已经通过PInvoke创建了你的窗口。主线程应该有一个标准PeekMessage循环,至少等待WM_QUIT和你定义的WM_EXECUTE_ANIMATE_WINDOW。通过PostMessage或PostThreadMessage构成此消息的辅助线程。

既然您已经发布了示例代码,那么您将遇到的问题是您没有尝试为任何窗口设置动画......但您正试图为控制台窗口设置动画......而且您不是在它的所有者线程上(否则当你在应用程序中创建一个无限循环时它不会刷新)...所以调用AnimateWindow是不可能的,除非你设法强迫windows在该线程上执行代码。

事实控制台窗口实际上归CSRSS所有。巫婆是一个以提升权利执行的系统流程,无论如何都会使它们变得非常危险。

由于windows vista,由于进程保护,甚至无法向这些窗口发送消息,因此任何以前可能被利用来强制此线程执行代码的漏洞现在都应该无法使用。

有关控制台窗口特性的详细信息,请参阅Raymond Chen博客上的Why aren't console windows themed on Windows XP?帖子(来自microsoft windows shell团队,因此它几乎来自源代码)

答案 1 :(得分:0)

通常在控制台应用中完成 。如果你正在尝试使用Win32 GUI API,我应该真正运行一个消息循环,我怀疑。

您可以从控制台应用调用Application.Run()Application.Run(ApplicationContext)来启动新的消息循环。我们的想法是使用SynchronizationContext.Current编组回主线程。但是,我还没有设法让它工作......你需要以某种方式强制它将其消息循环注册为当前的同步上下文,而我还没有设法说服它这样做:(

答案 2 :(得分:0)

我在考虑背景工作者,但是因为你没有一个不可能的UI线程(see this question

使用信号量的'经典'线程方法应该可行。使用线程安全队列或集合来存储事件,并通知主线程有工作要通过同步对象。