【问题标题】:Remove flickering from running consecutive exe files从运行连续的 exe 文件中删除闪烁
【发布时间】:2015-07-10 07:26:47
【问题描述】:

我有几个 .exe 文件,我运行如下:

   public void RunCalculator(Calculator calculator)
    {
        var query = Path.Combine(EpiPath, calculator.ExeName + ".exe");

        if (File.Exists(Path.Combine(EpiPath, "ffs.exe")))
        {
            var p = new Process();
            p.StartInfo.FileName = query;
            p.StartInfo.WorkingDirectory = EpiPath;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.StartInfo.Arguments = String.Join(" ", calculator.Arguments);
            p.Start();
            p.WaitForExit();
        }
        else throw new InvalidOperationException();
    }

此代码有效,但由于多次运行 exe-s 仍会导致一些闪烁。有什么办法可以消除闪烁,因为用户体验它真的很烦人,因为它发生了几秒钟(运行了很多 exe)。

我尝试使用一个任务在不同的线程上执行它,但由于每个 exe 都依赖于前一个的工作(它写入文件)我得到一个 IO 异常。

exe 文件似乎也只有在 ProcessPriority 设置为 Realtime 时才有效。

编辑:

关于我最近尝试的更多细节: 正如@Jack Hughes 建议的那样,我尝试使用后台工作人员来解决这个问题:

这就是我所做的:

我在后台工作人员上调用 RunWorkerAsync() 函数,然后它依次为每个计算器调用 RunCalculator 函数。闪烁仍然存在。

编辑2: 我创建了一个详细的存储库,其中包含我运行 exe 文件、exe 文件和 Old Fox 建议的 ProcessHelper 类的方式。 您可以在 README 文件中找到有关如何使用存储库的说明。

存储库链接:https://github.com/interdrift/epiwin-flick

【问题讨论】:

  • “闪烁”是什么意思?
  • exe 运行,它们执行一些计算和一些 IO 操作,然后它们关闭。在运行每个 exe 后,它会关闭,然后下一个会打开。他们每个人执行的操作都会导致整个 Windows 闪烁(我认为这与 Windows 的操作方式有关)。
  • 您是否正在从 GUI 线程启动上述和许多其他的?如果您在 GUI 线程上做了大量工作并等待结果,那么您就是在阻止 GUI 更新。当它最终开始自我更新时,它可能看起来像是在闪烁。将您的处理放入后台线程并尽可能保持主 GUI 线程空闲。
  • @JackHughes 您能否为此提供一个示例。我尝试使用一个任务在不同的线程上执行它,但由于每个 exe 都依赖于前一个的工作(它写入文件),我得到一个 IO 异常。
  • 如果您查看接受的答案here,您会找到一个很好的示例。

标签: c# windows winapi


【解决方案1】:

当我使用 C# 创建 ActiveX 扩展时,我遇到了与您描述的相同的“闪烁”问题。该扩展程序必须启动一个隐藏的Console Application,但是每次我启动该应用程序时,控制台都会出现几毫秒。

为了解决这个问题,我尝试了很多方法,例如:获取Process类的源代码并对其进行调试,UAC检查,VM的vs真机等等。

我找到的解决方案是使用Win32Api。下面的 sn -p 启动一个没有“闪烁”的新进程:

public class ProcessHelper
{

    public const Int32 USE_STD_HANDLES = 0x00000100;
    public const Int32 STD_OUTPUT_HANDLE = -11;
    public const Int32 STD_ERROR_HANDLE = -12;

    //this flag instructs StartProcessWithLogonW to consider the value StartupInfo.showWindow when creating the process
    public const Int32 STARTF_USESHOWWINDOW = 0x00000001;



    public static ProcessStartResult StartProcess(string exe,
                                                  string[] args = null,
                                                  bool isHidden = false,
                                                  bool waitForExit = false,
                                                  uint waitTimeout = 0)
    {
        string command;

        var startupInfo = CreateStartupInfo(exe, args, isHidden, out command);

        ProcessInformation processInfo;

        var processSecAttributes = new SecurityAttributes();

        processSecAttributes.Length = Marshal.SizeOf(processSecAttributes);

        var threadSecAttributes = new SecurityAttributes();

        threadSecAttributes.Length = Marshal.SizeOf(threadSecAttributes);

        CreationFlags creationFlags = 0;

        if (isHidden)
        {
            creationFlags = CreationFlags.CreateNoWindow;
        }

        var started = Win32Api.CreateProcess(exe,
                                                command,
                                                ref processSecAttributes,
                                                ref threadSecAttributes,
                                                false,
                                                Convert.ToInt32(creationFlags),
                                                IntPtr.Zero,
                                                null,
                                                ref startupInfo,
                                                out processInfo);


        var result = CreateProcessStartResult(waitForExit, waitTimeout, processInfo, started);

        return result;
    }

    private static StartupInfo CreateStartupInfo(string exe, string[] args, bool isHidden, out string command)
    {
        var startupInfo = new StartupInfo();

        startupInfo.Flags &= USE_STD_HANDLES;
        startupInfo.StdOutput = (IntPtr) STD_OUTPUT_HANDLE;
        startupInfo.StdError = (IntPtr) STD_ERROR_HANDLE;

        if (isHidden)
        {
            startupInfo.ShowWindow = 0;
            startupInfo.Flags = STARTF_USESHOWWINDOW;
        }

        var argsWithExeName = new string[args.Length + 1];

        argsWithExeName[0] = exe;

        args.CopyTo(argsWithExeName, 1);

        var argsString = ToCommandLineArgsString(argsWithExeName);

        command = argsString;

        return startupInfo;
    }

    private static string ToCommandLineArgsString(Array array)
    {
        var argumentsBuilder = new StringBuilder();

        foreach (var item in array)
        {
            if (item != null)
            {
                var escapedArgument = item.ToString().Replace("\"", "\"\"");
                argumentsBuilder.AppendFormat("\"{0}\" ", escapedArgument);
            }
        }

        return argumentsBuilder.ToString();
    }

    private static ProcessStartResult CreateProcessStartResult(bool waitForExit, uint waitTimeout,
        ProcessInformation processInfo, bool started)
    {
        uint exitCode = 0;
        var hasExited = false;

        if (started && waitForExit)
        {
            var waitResult = Win32Api.WaitForSingleObject(processInfo.Process, waitTimeout);

            if (waitResult == WaitForSingleObjectResult.WAIT_OBJECT_0)
            {
                Win32Api.GetExitCodeProcess(processInfo.Process, ref exitCode);
                hasExited = true;
            }
        }

        var result = new ProcessStartResult()
        {
            ExitCode = (int) exitCode,
            Started = started,
            HasExited = hasExited
        };
        return result;
    }

}

[Flags]
public enum CreationFlags
{
    CreateSuspended = 0x00000004,

    CreateNewConsole = 0x00000010,

    CreateNewProcessGroup = 0x00000200,

    CreateNoWindow = 0x08000000,

    CreateUnicodeEnvironment = 0x00000400,

    CreateSeparateWowVdm = 0x00000800,

    CreateDefaultErrorMode = 0x04000000,
}

public struct ProcessInformation
{
    public IntPtr Process { get; set; }
    public IntPtr Thread { get; set; }
    public int ProcessId { get; set; }
    public int ThreadId { get; set; }
}

public class ProcessStartResult
{
    public bool Started { get; set; }

    public int ExitCode { get; set; }

    public bool HasExited { get; set; }

    public Exception Error { get; set; }

}

[StructLayout(LayoutKind.Sequential)]
public struct SecurityAttributes
{
    public int Length;
    public IntPtr SecurityDescriptor;
    public int InheritHandle;
}

public struct StartupInfo
{
    public int Cb;
    public String Reserved;
    public String Desktop;
    public String Title;
    public int X;
    public int Y;
    public int XSize;
    public int YSize;
    public int XCountChars;
    public int YCountChars;
    public int FillAttribute;
    public int Flags;
    public UInt16 ShowWindow;
    public UInt16 Reserved2;
    public byte Reserved3;
    public IntPtr StdInput;
    public IntPtr StdOutput;
    public IntPtr StdError;
}

public static class WaitForSingleObjectResult
{
    /// <summary>
    /// The specified object is a mutex object that was not released by the thread that owned the mutex
    /// object before the owning thread terminated. Ownership of the mutex object is granted to the 
    /// calling thread and the mutex state is set to nonsignaled
    /// </summary>
    public const UInt32 WAIT_ABANDONED = 0x00000080;
    /// <summary>
    /// The state of the specified object is signaled.
    /// </summary>
    public const UInt32 WAIT_OBJECT_0 = 0x00000000;
    /// <summary>
    /// The time-out interval elapsed, and the object's state is nonsignaled.
    /// </summary>
    public const UInt32 WAIT_TIMEOUT = 0x00000102;
}

public class Win32Api
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);

    [DllImport("kernel32.dll")]
    public static extern bool CreateProcess
        (string lpApplicationName,
            string lpCommandLine,
            ref SecurityAttributes lpProcessAttributes,
            ref SecurityAttributes lpThreadAttributes,
            bool bInheritHandles,
            Int32 dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            [In] ref StartupInfo lpStartupInfo,
            out ProcessInformation lpProcessInformation);
}

编辑:

我用startupInfo.ShowWindow = 7尝试了上面的代码(它应该在不窃取焦点的情况下启动应用程序),一些应用程序仍然在窃取焦点,因此我推断其中一些使用了一种Bring to front method... .

我在WPF 窗口中使用了你的代码,然后我发现如果UI 在Sleep 中,应用程序不会失去焦点:

    private  void Button_Click(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(() =>
        {

            //Provide path to epi
            string epiPath = @"c:/EPISUITE41";
            Level3ntCalculator cl = new Level3ntCalculator();
            var runner = new Calculators.Epi.Runners.ProcessRunner(epiPath);


            runner.WriteXSmilesFiles("CCCCC1CCCCCCC1");
                    cl.Calculate(runner);

        });

        Thread.Sleep(2000);
    }

这不是一个好的解决方案!但是,仅当您启动其中一个进程时才需要进行睡眠...所以我尝试使用此信息:

    private  void Button_Click(object sender, RoutedEventArgs e)
    {
        var ac = new Action(() => Application.Current.Dispatcher.Invoke(
            () =>
            {
                Thread.Sleep(50);
            }));
        Task.Factory.StartNew(() =>
        {

            //Provide path to epi
            string epiPath = @"c:/EPISUITE41";
            Level3ntCalculator cl = new Level3ntCalculator();
            var runner = new Calculators.Epi.Runners.ProcessRunner(epiPath, ac);


            runner.WriteXSmilesFiles("CCCCC1CCCCCCC1");
                    cl.Calculate(runner);

        });

    }

然后我换了ProcessRunner:

    public void RunCalculator(Calculator calculator)
    {
     //bla bla bla...
        new Thread(new ThreadStart(_action)).Start();
        p.Start();
     //...
    }

在这个 sn-p 中,您仅在很短的时间内在 UI 线程中执行 Thread.Sleep(用户永远不会知道...)。

这是一个糟糕的解决方案......但是它解决了问题。

【讨论】:

  • Exe的不使用这种方法运行,可能它改变了进程的优先级?不太清楚为什么。
  • 您如何执行方法ProcessHelper.StartProcess?(行的样子)请确认该进程未在Task Manager(进程选项卡)中启动。如果您愿意,我可以将示例上传到 github。
  • 为什么不应在任务管理器进程中启动该进程?无论如何,很难跟踪进程是否已经开始,因为它几乎立即退出。
  • 我的错,你应该在Task Manager(进程选项卡)中看到它,但你不应该在Applications 选项卡中看到它。现在我正在使用一台linux机器。我稍后会上传一个工作示例(大约 5 小时后)
  • 似乎很有希望。我会看看它是否适合用例并回复。你应该得到赏金,尽管这个解决方案可能会被证明是有用的,但我想我最终不得不伪装成应用程序来看看可以做什么。 PS:我已经开发了一个并行版本的应用程序,其中包含多个带有 exe-s 的此类文件夹。灯光秀很棒。
【解决方案2】:

将工作置于后台线程中,这样您就不会阻塞主 GUI 线程。

类似这样的:

public class MainWindow : Window
{
    private readonly BackgroundWorker worker = new BackgroundWorker();

    public MainWindow()
    {
        this.Loaded += MyWindow_Loaded;
        worker.DoWork += worker_DoWork;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
    }

    private void MyWindow_Loaded(object sender, RoutedEventArgs e)
    {
        // Start the background worker. May be better started off on a button or something else app specific
        worker.RunWorkerAsync();
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
       // run all .exes here...
       // Note: you cannot access the GUI elements directly from the background thread
       RunCalculator();
       RunOtherExes();
    }

    private void worker_RunWorkerCompleted(object sender, 
                                           RunWorkerCompletedEventArgs e)
    {
        //update ui once worker complete its work
    }

    private void RunCalculator()
    {
        var p = new Process();
        p.StartInfo.FileName = query;
        p.StartInfo.WorkingDirectory = path;
        p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.Arguments = String.Join(" ", calculator.Arguments);
        p.Start();
        p.WaitForExit();
    }

    private void RunOtherExes()
    {
        // ...
    }

}

如果需要,您也可以使用后台线程的进度更新 GUI 线程。

【讨论】:

  • 如果我这样做,exe文件的执行顺序会同步吗?我需要它同步才能获得正确的输出。这意味着在执行 exe 文件 B 之前,我必须确保 exe 文件 A 已完成。
  • 是的。 RunCalculator 首先执行,并在进程上调用WaitForExit,以便后台线程等待进程结束后再开始下一个进程。然后调用RunOtherExes 对其他进程执行相同操作。
  • 不幸的是使用这种方法仍然会导致闪烁。
  • 你能发布剩下的项目吗?
  • 由于保密协议,我不能发布我正在进行的项目。我在我的问题中解释了大部分实现。感谢您的回复。
【解决方案3】:

目前还不是很清楚您正在经历什么样的闪烁,但是您在 GUI 线程上执行 WaitForExit() 的事实看起来很麻烦。鉴于您正在运行一个外部程序,我并没有真正看到从另一个线程启动它并等待的意义,也许使用 Exited 事件而不是阻塞等待会释放 GUI 线程来完成它的工作并停止问题。

不要忘记将 EnableRaisingEvents 设置为 true,IIRC 事件将在线程池中引发,因此在触摸任何控件之前返回 UI 上下文。

【讨论】:

  • 我在没有 WaitForExit 的情况下尝试了同样的事情,但有一个超时,结果是一样的。
  • 无论是否超时,UI 线程上的任何等待都可能导致问题。
猜你喜欢
  • 2017-03-08
  • 2017-09-11
  • 2022-09-28
  • 1970-01-01
  • 1970-01-01
  • 2021-07-17
  • 2021-03-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多