Подтвердить что ты не робот

Удалить мерцание от запуска последовательных файлов EXE

У меня есть несколько файлов .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.

Edit:

Несколько подробнее о том, что я пробовал в последнее время: Поскольку @Jack Hughes предположил, что я попытался использовать фонового работника для решения этой проблемы:

Вот что я сделал:

Я вызываю функцию RunWorkerAsync() для рабочего фона, которая на ней вызывает функцию RunCalculator для каждого из калькуляторов в порядке. Мерцание все еще сохраняется.

Edit2: Я создал подробный репозиторий, который содержит мой способ запуска exe файлов, exe файлов и класса ProcessHelper, который предложил Old Fox. Вы можете найти инструкции по использованию репозитория внутри файла README.

Ссылка на репозиторий: https://github.com/interdrift/epiwin-flick

4b9b3361

Ответ 1

У меня была такая же "мерцающая" проблема, как вы описали, когда я создал расширение ActiveX, используя С#. Расширение должно было запустить скрытый Console Application, однако каждый раз, когда я запускал приложение, консоль появлялась в течение нескольких мс.

Чтобы решить эту проблему, я пробовал много вещей, таких как: взяв исходный код класса Process и отлаживая его, UAC check, VM vs Real machines и т.д.

Решение, которое я нашел, это использовать Win32Api. Следующий фрагмент запускает новый процесс без "мерцания":

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 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);
}

Edit:

Я пробовал приведенный выше код с 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();
     //...
    }

В этом фрагменте вы выполняете Thread.Sleep в потоке UI только за очень короткое время (пользователь никогда не узнает...).

Это плохое решение... Однако он решил проблему.

Ответ 2

Поместите произведение в фоновый поток, чтобы не блокировать основной поток графического интерфейса.

Что-то вроде этого:

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()
    {
        // ...
    }

}

Вы также можете обновить поток графического интерфейса с прогрессом из фонового потока, если вам это нужно.

Ответ 3

До сих пор не совсем ясно, какое мерцание вы испытываете, однако тот факт, что вы делаете WaitForExit() в потоке графического интерфейса, выглядит довольно проблематичным. Учитывая, что вы используете внешнюю программу, я действительно не вижу смысла запускать ее из другого потока и ждать, возможно, использование события Exited вместо блокировки ожидания приведет к выпуску потока GUI для выполнения своей работы и прекращения этой проблемы.

Не забудьте установить EnableRaisingEvents значение true и IIRC, событие будет поднято в пуле потоков, поэтому вернитесь в контекст пользовательского интерфейса, прежде чем прикоснуться к любым элементам управления.