Приложение не выйдет до того, как Diagnostics.Process.Start () закончит

У меня есть приложение, которое открывает файл Excel .xlsm. Файл Excel содержит кучу долгого кода в Auto_Open (), который может занять несколько минут.

В настоящее время я открываю файл excel, и пока макрос все еще работает, я выхожу из приложения. Главное окно закрывается, но я вижу, что MyApp.exe запущен в диспетчере задач до тех пор, пока макросы не закончатся, после чего заканчивается процесс MyApp.exe.

private void btnOpenExcel_Click(object sender, RoutedEventArgs e) { System.Diagnostics.Process.Start(excelFilePath); } private void btnClose_Click(object sender, RoutedEventArgs e) { Application.Current.Shutdown(); //Also tried this.Close(); } 

Я хотел бы иметь возможность открыть файл Excel, а затем выйти из моего приложения, не дожидаясь завершения макросов Excel. Это возможно?

После достаточного количества экспериментов и исследований я знаю, что происходит. Это неудачный побочный эффект реализации Excel и способ использования собственной функции Windows ShellExecuteEx (используемой System.Diagnostics.Process ). В частности, Excel не будет подтверждать завершение команды DDE для ShellExecuteEx до тех пор, auto_run макрос auto_run не завершится, а при вызове auto_run который использует класс Process ShellExecuteEx не вернется, пока это не произойдет или (очень важно), до двух минут.

(В документации указано, что есть одноминутный тайм-аут, но на моей машине с Windows 8.1 это было две минуты).

Я обнаружил несколько обходов, ни один из которых не был полностью изящным, но все они должны работать нормально.

ПРИМЕЧАНИЕ. Все приведенные ниже примеры кода будут помещены в обработчик события клика, за исключением случаев, когда указано иное (т.е. объявления interop).

Моя предпочтительная работа – просто использовать отдельный поток, чтобы начать процесс. Это не решает проблему самого процесса. Но остальная часть процесса может закрыться, оставив одинокую нить ждать в таймаут (или завершение auto_open , в зависимости от того, что наступит раньше):

 Thread thread = new Thread(() => Process.Start(target)); thread.IsBackground = false; thread.SetApartmentState(ApartmentState.STA); thread.Start(); 

Одна из проблем, возникающих при отсутствии ShellExecuteEx , заключается в том, что Windows на самом деле нуждается в потоке STA для зависания достаточно долго, чтобы она выдала команду DDE в Excel, чтобы открыть данный файл. Это означает, что любая попытка обхода задержки в ShellExecuteEx запускает риск того, что Excel не начнет вообще или не откроет запрошенный файл.

Тем не менее, если вы согласны принять этот риск или смягчить его с помощью более длительных тайм-аутов (но не обязательно, пока накладывается двухминутный тайм-аут Windows), есть несколько других подходов, которые вы можете предпринять.

Второй подход заключался бы в очереди вызова Close() для последующего выполнения. Это использует тот факт, что ShellExecuteEx по-прежнему работает с насосом сообщений, поэтому, несмотря на то, что Process.Start() не вернулся, вы все равно можете получить код для выполнения в подклассе Form . Примером этого может быть:

 BeginInvoke((Action)(async () => { await Task.Delay(1000); Close(); })); Process.Start(target); 

Это задерживает команду Close() течение одной секунды, которая на моем компьютере была достаточно длинной, чтобы позволить Process и ShellExecuteEx выполнять работу, необходимую для запуска Excel.

ПРИМЕЧАНИЕ. Я попытался сократить время ожидания и нашел его ненадежным. То есть, при 100 мс вместо 1000 мс, Excel не начинался вообще. В 500 мс это началось, но часто фактически не загружало книгу. На второй секунде он был надежен на моем ноутбуке, оборудованном SSD. Я не знаю, где латентность, но может быть, что на машине с более медленным приводом потребуется более длительный тайм-аут.

Единственное, что мне не нравится в приведенном выше, заключается в том, что он срывает приложение до того, Process.Start() метод Process.Start() действительно вернулся. Хотя это работает, это кажется слишком слишком далеко от линии «кошерная / не кошерная». 🙂

Таким образом, третий вариант заключается в том, чтобы обойти весь класс Process и напрямую вызвать ShellExecuteEx . Выполняя это так, вам все равно нужно подождать, иначе Excel не запустится надежно. Но вы можете сделать ожидание после завершения вызова ShellExecuteEx , и поэтому очистка приложения кажется мне более чистой. То есть, это абсолютно нормальный выход из программы, позволяющий все обычное домашнее хозяйство, которое вы, возможно, захотите сделать.

Это немного больше из-за заявлений interop, но оно работает хорошо:

 SHELLEXECUTEINFO sei = new SHELLEXECUTEINFO(); sei.fMask = ShellExecuteMaskFlags.SEE_MASK_FLAG_NO_UI; sei.nShow = ShowCommands.SW_NORMAL; sei.lpFile = target; if (!Interop.ShellExecuteEx(sei)) { int hr = Marshal.GetLastWin32Error(); Exception e = Marshal.GetExceptionForHR(hr); // Throw, display message box, whatever you like here } await Task.Delay(100); Close(); 

где объявления interop выглядят так (неиспользуемые значения перечисления опущены):

 class Interop { [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool ShellExecuteEx(SHELLEXECUTEINFO lpExecInfo); } [StructLayout(LayoutKind.Sequential)] public class SHELLEXECUTEINFO { public int cbSize; public ShellExecuteMaskFlags fMask; public IntPtr hwnd; [MarshalAs(UnmanagedType.LPTStr)] public string lpVerb; [MarshalAs(UnmanagedType.LPTStr)] public string lpFile; [MarshalAs(UnmanagedType.LPTStr)] public string lpParameters; [MarshalAs(UnmanagedType.LPTStr)] public string lpDirectory; public ShowCommands nShow; public IntPtr hInstApp; public IntPtr lpIDList; [MarshalAs(UnmanagedType.LPTStr)] public string lpClass; public IntPtr hkeyClass; public uint dwHotKey; public IntPtr hIcon; public IntPtr hProcess; public SHELLEXECUTEINFO() { this.cbSize = Marshal.SizeOf(this); } } public enum ShowCommands : int { SW_NORMAL = 1, } [Flags] public enum ShellExecuteMaskFlags : uint { SEE_MASK_FLAG_NO_UI = 0x00000400, } 

Используя этот метод, я смог использовать намного более короткий тайм-аут. Кажется, что 100 мс работают надежно, а 10 мс – нет.

Наконец, обратите внимание, что если вы можете изменить книгу Excel, вы должны иметь возможность настроить таймер в процедуре auto_open которая затем запускает фактический код инициализации, позволяя auto_open возвращать auto_open . Это может отрицательно повлиять на запуск кода запуска в вашей программе на C #. 🙂

Простейшим способом может быть вызов Environment.Exit (-1), который завершает процесс и дает базовой операционной системе указанный код выхода.

  • Экспорт данных в файл Excel
  • Пользовательская вкладка ленты в excel 2010 не появляется после установки vsto c #
  • Чтение объекта excel в файле excel с использованием aspose error
  • Как включить опции «Совместное использование» в книгах Excel?
  • Как импортировать данные файла excel в таблицу данных в c #
  • большой экспорт datagridview в excel
  • Надстройка VSTO в Mac Excel
  • Как эффективно использовать WorkbookBeforeClose событие правильно?
  • Использование Microsoft.Office.Interop для сохранения созданного файла с помощью C #
  • Как изменить размер метки данных в таблице Excel C #
  • Автоматизация Excel: как установить диапазон?
  • Давайте будем гением компьютера.