启动一个进程,如果其中一个线程占用 CPU,请挂起它



有一个众所周知的问题,Windows 8上的Skype在某些用户的PC上占用了一个CPU内核的100%。 Skype社区中有一个由techfreak提供的解决方法:

  1. 下载并运行最新版本的进程资源管理器。 (http://download.sysinternals.com/files/ProcessExplorer.zip)
  2. 在Skype
  3. 运行搜索Skype的情况下.exe在活动程序列表中并双击它。
  4. 转到线程选项卡,然后挂起或终止空闲时消耗最多资源的 Skype 线程。(如 50%+ CPU)

每次重新启动后手动执行此操作都会让我感到恼火,因此我想自动执行上述步骤,编写一个简单的C++或 C#"Skype 启动器"程序来执行以下操作:

  • 启动 SKYPE
  • .EXE
  • 每 1 秒唤醒一次,并查看某个特定的 Skype 线程是否占用了进程中 98% 以上的 CPU 周期
  • 如果找到,请挂起该线程并退出启动程序进程
  • 否则最多循环 10 次,直到找到坏线程。

在快速的谷歌搜索之后,我被 Win32 线程枚举 API 吓倒了,这个"查找并杀死/挂起邪恶线程"的问题似乎相当通用,所以我想知道是否有一个现有的示例我可以重新利用。 有什么指示吗?

经过更多的谷歌搜索和Powershell(太多的安全麻烦,对新手来说太混乱)和WMI(比需要的更难)的一些死胡同,我终于在MSDN论坛上找到了一个很棒的C#示例,它将枚举和挂起线程。这很容易适应在挂起罪魁祸首之前首先检查每个线程的 CPU 时间。

下面是代码。只需编译并放入您的启动菜单,Skype 将不再为您的办公室供暖!

// code adapted from 
// http://social.msdn.microsoft.com/Forums/en-US/d51efcf0-7653-403e-95b6-bf5fb97bf16c/suspend-thread-of-a-process
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
using System.ComponentModel;
namespace SkypeLauncher
{
    class Program
    {
        static void Main(string[] args)
        {
            Process[] procs = Process.GetProcessesByName("skype");
            if (procs.Length == 0)
            {
                Console.WriteLine("Skype not loaded. Launching. ");
                Process.Start(Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES(X86)%SkypePhoneSkype.exe"));
                Thread.Sleep(8000); // wait to allow skype to start up & get into steady state
            }
            // wait to allow skype to start up & get into steady state, where "steady state" means
            // a lot of threads created
            Process proc = null;
            for (int i = 0; i < 50; i++)
            {
                procs = Process.GetProcessesByName("skype");
                if (procs != null)
                {
                    proc = procs[0];
                    if (proc.Threads.Count > 10)
                        break;
                }
                Thread.Sleep(1000); // wait to allow skype to start up & get into steady state
            }
            // try multiple times; if not hanging after a while, give up. It must not be hanging!
            for (int i = 0; i < 50; i++)    
            {
                // must reload process to get updated thread time info
                procs = Process.GetProcessesByName("skype");
                if (procs.Length == 0)
                {
                    Console.WriteLine("Skype not loaded. Exiting. ");
                    return;
                }
                proc = procs[0];
                // avoid case where exception thrown if thread is no longer around when looking at its CPU time, or
                // any other reason why we can't read the time
                var safeTotalProcessorTime = new Func<ProcessThread, double> (t => 
                    {
                        try { return t.TotalProcessorTime.TotalMilliseconds; }
                        catch (InvalidOperationException) { return 0; }
                    }
                );
                var threads = (from t in proc.Threads.OfType<ProcessThread>()
                                  orderby safeTotalProcessorTime(t) descending
                                  select new  
                                  {
                                      t.Id, 
                                      t.ThreadState, 
                                      TotalProcessorTime = safeTotalProcessorTime(t),  
                                  } 
                              ).ToList();
                var totalCpuMsecs = threads.Sum(t => t.TotalProcessorTime);
                var topThread = threads[0];
                var nextThread = threads[1];
                var topThreadCpuMsecs = topThread.TotalProcessorTime;
                var topThreadRatio = topThreadCpuMsecs / nextThread.TotalProcessorTime;
                // suspend skype thread that's taken a lot of CPU time and 
                // and it has lots more CPU than any other thread. 
                // in other words, it's been ill-behaved for a long time!
                // it's possible that this may sometimes suspend the wrong thread, 
                // but I haven't seen it break yet. 
                if (topThreadCpuMsecs > 10000 && topThreadRatio > 5)
                {
                    Console.WriteLine("{0} bad thread. {0:N0} msecs CPU, {1:N1}x CPU than next top thread.",
                        topThread.ThreadState == System.Diagnostics.ThreadState.Wait ? "Already suspended" : "Suspending",
                        topThreadCpuMsecs, 
                        topThreadRatio);
                    Thread.Sleep(1000);
                    IntPtr handle = IntPtr.Zero;
                    try
                    {
                        //Get the thread handle & suspend the thread
                        handle = OpenThread(2, false, topThread.Id);
                        var success = SuspendThread(handle);
                        if (success == -1)
                        {
                            Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error());
                            Console.WriteLine(ex.Message);
                        }
                        Console.WriteLine("Exiting");
                        Thread.Sleep(1000);
                        return;
                    }
                    finally
                    {
                        if (handle != IntPtr.Zero)
                            CloseHandle(handle);
                    };
                }
                Console.WriteLine("Top thread: {0:N0} msecs CPU, {1:N1}x CPU than next top thread. Waiting.",
                    topThreadCpuMsecs,
                    topThreadRatio);
                Thread.Sleep(2000); // wait between tries
            }
            Console.WriteLine("No skype thread is ill-behaved enough. Giving up.");
        }
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern int SuspendThread(IntPtr hThread);
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern
        IntPtr OpenThread(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)]bool bInheritHandle, int dwThreadId);
    }
}

最新更新