【问题标题】:C# backgroundworker stops by itselfC# backgroundworker 自行停止
【发布时间】:2015-01-13 16:15:48
【问题描述】:

我用 C# 编写了一个启用多个全局热键的程序,并根据按下的热键激活 chrome、firefox、记事本、计算器等窗口。注册全局热键后,我有一个无限的时间-使应用程序保持活动状态的循环。运行程序后,热键突然停止工作。这有时会在几个小时后发生。经过长时间测试我的每一段代码后,我发现了问题。问题是主线程突然停止工作。该程序仍然存在并在内存中。热键似乎是在另一个活动的线程中注册的,即使包含 while 循环的主线程死了,它也能保持程序运行。

然后我使用了一个后台工作程序并在后台工作程序中移动了 while 循环。它再次发生,这意味着后台工作人员在热键仍在注册时突然停止。这不是我第一次使用 backgroudworker,而且我从未遇到过这样的事情,即 backgroundworker 会自行退出。

发生这种情况时,类似这样的消息会出现在 Visual Studio 的输出窗口中:

The thread 0x1b24 has exited with code 0 (0x0)

线程是否有任何时间限制,以便它们在此之后退出? 您对这是如何发生的以及如何解决它有什么建议吗?

对于全局热键,我使用此处列出的代码:

http://stackoverflow.com/a/3654821/3179989

这是我剩下的代码:

    public static void HotKeyPressed(object sender, HotKeyEventArgs e)
    {
        string PressedHotkey = e.Modifiers.ToString() + " " + e.Key.ToString();
        switch (PressedHotkey)
        {
            case "Alt D1":
                mActivateWindow(mEnumApplications.Chrome);
                break;
            case "Alt D3":
                mActivateWindow(mEnumApplications.CintaNotes);
                break;
            default:
                break;
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        bgWkrHotkey.WorkerSupportsCancellation = true;
        bgWkrHotkey.WorkerReportsProgress = true;
        bgWkrHotkey.RunWorkerAsync();
    }

    private void bgWkrHotkey_DoWork(object sender, DoWorkEventArgs e)
    {
        mHotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Alt);
        mHotKeyManager.RegisterHotKey(Keys.D1, KeyModifiers.Alt);
        mHotKeyManager.RegisterHotKey(Keys.D3, KeyModifiers.Alt);
        mHotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyPressed);

        while (true)
        {
            Thread.Sleep(50);
        }
    }

    //@@@@@@@@@@@@@@@@@@@@ DLL IMPORTS @@@@@@@@@@@@@@@@@@@@
    #region DLL IMPORTS
    [DllImport("User32.dll")]
    private static extern IntPtr SetForegroundWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

    delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
    [DllImport("USER32.DLL")]
    static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

    [DllImport("USER32.DLL")]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("USER32.DLL")]
    static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("USER32.DLL")]
    static extern IntPtr GetShellWindow();
    #endregion DLL IMPORTS

    public static IDictionary<IntPtr, string> mGetOpenWindows()
    {
        IntPtr ipShellWindow = GetShellWindow();
        Dictionary<IntPtr, string> ipWindows = new Dictionary<IntPtr, string>();

        EnumWindows(delegate(IntPtr hWnd, int lParam)
        {
            if (hWnd == ipShellWindow) return true;
            //if (!IsWindowVisible(hWnd)) return true;

            int lLength = GetWindowTextLength(hWnd);
            if (lLength == 0) return true;

            StringBuilder lBuilder = new StringBuilder(lLength);
            GetWindowText(hWnd, lBuilder, lLength + 1);

            ipWindows[hWnd] = lBuilder.ToString();
            return true;

        }, 0);

        return ipWindows;
    }

    public static string mGetActiveWindowTitle()
    {
        const int nChars = 256;
        StringBuilder Buff = new StringBuilder(nChars);
        IntPtr handle = GetForegroundWindow();

        if (GetWindowText(handle, Buff, nChars) > 0)
        {
            return Buff.ToString();
        }
        return "";
    }


    public static bool mActivateWindow(IntPtr ipHandle, string strWindowTitle)
    {
        StringBuilder Buff = new StringBuilder(256);
        SetForegroundWindow(ipHandle);

        Stopwatch swTimeout = new Stopwatch();
        swTimeout.Start();
        while (swTimeout.Elapsed < TimeSpan.FromSeconds(2))
        {
            ipHandle = GetForegroundWindow();
            if ((GetWindowText(ipHandle, Buff, 256) > 0) && (Buff.ToString().ToLower().Contains(strWindowTitle.ToLower())))
                return true;
            else
            {
                SetForegroundWindow(ipHandle);
                Thread.Sleep(50);
            }
        }
        swTimeout.Stop();
        return false;
    }

    public static bool mActivateWindow(mEnumApplications enumApp)
    {
        string strWindowTitle = "";
        switch (enumApp)
        {
            case mEnumApplications.Chrome:
                strWindowTitle = "Google Chrome";
                break;
            case mEnumApplications.CintaNotes:
                strWindowTitle = "CintaNotes";
                break;
            default:
                break;
        }

        IntPtr ipHandle = IntPtr.Zero;
        string strExactTitle = "";
        StringBuilder Buff = new StringBuilder(256);
        foreach (KeyValuePair<IntPtr, string> ipWindow in mGetOpenWindows())
        {
            ipHandle = ipWindow.Key;
            strExactTitle = ipWindow.Value;

            if (strExactTitle.ToLower().Contains(strWindowTitle.ToLower()))
                if (mActivateWindow(ipHandle, strWindowTitle))
                    return true;

        }
        return false;
    }

    public enum mEnumApplications
    {
        Null,
        Chrome,
        CintaNotes,
    };

感谢您的帮助。 谢谢

【问题讨论】:

  • 您的主线程发生了无法解释的崩溃,因此您将热键移至BackgroundWorker?似乎您应该诊断出主线程崩溃。转到BackgroundWorker 只是使问题翻了一番(至少)。也就是说,我怀疑您的热键按下处理程序中的某些东西正在引发异常。在它周围放置一个 try/catch 并记录你得到的任何异常。
  • 此外,我会仔细查看您的EnumWindows 回调。为您的StringBuilder 分配内存是可疑的,特别是因为您将其分配给lLength,但随后将lLength+1 传递给GetWindowText。这很可能会尝试覆盖您不拥有的内存,并导致线程崩溃。 GetWindowTextLength 没有具体说明返回值是否包含空终止符。很可能将返回值加 1 并使用结果初始化您的 StringBuilder 将解决问题。
  • 感谢您的 cmets。那部分代码实际上不是我自己的代码,我只是从互联网上复制的 (blog.tcx.be/2006/05/getting-list-of-all-open-windows.html)。对不起,我应该在我的主要帖子中引用这一点。您能否更改代码并将其作为答案发送,因为我不确定您的意思?谢谢
  • EnumWindows 调用中,将此new StringBuilder(lLength); 更改为new StringBuilder(lLength+1);
  • 我改变了它。现在它正在运行。一旦我确定问题是否解决,我会回复。谢谢。

标签: c# multithreading hotkeys


【解决方案1】:

查看代码,错误可能不在于您调用GetWindowTextLength 的方式。你有:

int lLength = GetWindowTextLength(hWnd);
if (lLength == 0) return true;

所以如果GetWindowTextLength 有错误,你的函数就会返回。

但是,您分配StringBuilder 的方式存在错误。如果您查看GetWindowTextLength 页面上的 cmets,您会看到返回的值不包含空终止符。所以当你分配你的StringBuilder 时,你得到的字符太小了。你的代码应该是:

StringBuilder lBuilder = new StringBuilder(lLength+1);  // <-- changed to lLength+1
GetWindowText(hWnd, lBuilder, lLength + 1);

如果您不进行该更改,那么对 GetWindowText 的调用可能会覆盖您的缓冲区,这将导致崩溃。

一个潜在的问题是您的托管原型适用于 32 位,但不适用于 64 位。例如,您有:

delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

这对于 32 位来说没问题,因为 lParam 是 32 位。但在 64 位中,lParam 是 64 位。该原型应该是:

delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

与您的 EnumWindows 原型类似。应该是:

[DllImport("USER32.DLL")]
static extern bool EnumWindows(EnumWindowsProc enumFunc, IntPtr lParam);

当你调用它时,为参数指定IntPtr.Zero,而不是0

奇怪的是你不能捕获错误。如果您已经修复了我上面指出的问题并且您仍然收到错误,我建议您找错地方了。

特别是,绝对没有理由需要为热键设置单独的线程。您应该能够在主程序中定义键,并且只要主程序运行,热键就会起作用。添加线程只会混淆问题。

除此之外,在您查明导致问题的确切原因之前,我无法提供更多帮助。您需要检查非托管函数调用的每个返回值。您还应该考虑添加一些日志记录,并记录每个操作。这样您就可以更轻松地确定错误发生的位置。

【讨论】:

  • 非常感谢 Jim 提供的信息丰富的回答。我根据您的 cmets 修改了代码,但是每当我按下热键(例如 Alt+1)时它仍然会崩溃。我没有添加任何线程。我按照您的建议删除了后台工作人员。我正在运行的代码是我的主要代码的简化版本,我希望从中看到更好的结果,但它似乎变得更糟。我将解决方案文件夹上传到我的保管箱中,我想知道您是否可以快速查看它:https://db.tt/3FFRBrpL。谢谢你。
  • 我忘了提到Button1 激活了热键。一旦它们被激活,按下Alt+1 会使所有热键无效(因为崩溃)! Button3 只是执行ActiveWindows() 方法,它可以正常工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多