Обратная связь для Excel Automation и выполнение длительного макроса

У меня самая ужасная задача - автоматизировать книгу Excel в C#. Частью процесса является вызов макроса в рабочей книге, выполнение которого занимает около 2 минут. Все это происходит в службе Windows и успешно выполняется от начала до конца. В настоящее время он записывает события в таблицу базы данных непосредственно перед вызовом макроса и после завершения макроса. Он выполняет много вычислений и экспортирует данные в текстовый файл в коде макроса.

Поскольку это занимает так много времени, пользователи спрашивают, могут ли они быть уведомлены на различных этапах процесса.

Моя первоначальная мысль с резервированием состояла в том, чтобы периодически опрашивать Application.StatusBar, который получает обновления при запуске макроса с использованием System.Timers.Timer. Я подумал, что с этим могут возникнуть какие-то проблемы с потоками - что, я думаю, происходит, поскольку вызов таймера для получения StatusBar не возвращается / не завершается в течение довольно большого периода времени (десятки секунд).

Моя книга завернута в следующий класс, который обеспечивает корректное закрытие Excel и запуск макроса:

internal class myWorkbook : IDisposable
{
    private Microsoft.Office.Interop.Excel.Application app = null;
    private Microsoft.Office.Interop.Excel.Workbook myWorkbook = null;
    private string _myWorkbookUri;

    public myWorkbook(string myWorkbookUri, string)
    {
        _myWorkbookUri = myWorkbookUri;
    }

    public string Export(DateTime date)
    {
        app = new Microsoft.Office.Interop.Excel.Application();
        app.Visible = false;
        app.DisplayAlerts = false;
        app.Interactive = false;
        app.AskToUpdateLinks = false;
        myWorkbook = app.Workbooks.Open(_myWorkbookUri, 0, true, 5, "", "", false, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "", true, false, 0, true, true, false);

        return (string)app.Run("GenerateTextFile", date);       
    }

    /// <summary>
    /// Disposes the object instance and all unmanaged resources.
    /// </summary>
    void IDisposable.Dispose()
    {
        if (myWorkbook != null)
        {
            myWorkbook.Close(false);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(myWorkbook);
        }

        if (app != null)
        {
            app.Quit();
            System.Runtime.InteropServices.Marshal.ReleaseComObject(app);
        }
    }

    public string Status
    {
        get
        {
            if (myWorkbook == null)
                return string.Empty;
            else
            {   
                return myWorkbook.Application.StatusBar.ToString();
            }
        }
    }
}

Затем я попытался выполнить мониторинг, используя следующий вложенный / внутренний класс в моем классе обработки отчетов:

private class MyMonitor : System.Timers.Timer
{
    private MyWorkbook _wb;
    private ReportGeneratorProcess _parent;
    private string _lastStatus = string.Empty;
    private bool handlingTimer = false;

    public MyMonitor(MyWorkbook wb, ReportGeneratorProcess parent)
    {
        _wb = wb;
        _parent = parent;
        this.AutoReset = true;
        this.Elapsed += new System.Timers.ElapsedEventHandler(this.timer_Elapsed);                
    }

    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        if (_wb != null && !handlingTimer)
        {
            try
            {
                handlingTimer = true;
                string status = _wb.Status;
                if (status != _lastStatus && status.ToLower() != "false")
                    _parent.AddEvent(MSG_TITLE_RUN_My, status);

                _lastStatus = status;
            }
            finally
            {
                handlingTimer = false;
            }
        }
    }
}

Это все вызвано выполнением следующего кода в классе ReportGeneratorProcess (я опустил ненужный код)

string outputFilename = null;
MyMonitor monitor = null;

try
{
    using (MyWorkbook wb = new MyWorkbook(_MyWorkbookUri))
    {
        monitor = new MyMonitor(wb, this);
        monitor.Start();
        outputFilename = wb.Export(month);
        monitor.Stop();
    }

    AddEvent("My Complete", "Generated file " + outputFilename);
    return outputFilename;
}
catch (Exception ex)
{
    AddEvent("", "failed");
    throw ex;
}
finally
{
    if (monitor != null)
    {
        monitor.Stop();
    }
}

AddEvent просто добавляет новое событие в базу данных, используя основной класс.

Я остаюсь побежденным в данный момент, пытаясь придумать альтернативное решение / хороший способ обойти это. У кого-нибудь есть намеки?

К сожалению, процесс должен работать как есть. Там нет места, чтобы переместить что-либо из Excel.

2 ответа

Решение

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

Рабочая книга создает файл журнала локально на сервере. В прошлом у меня были проблемы с FileSystemWatcher, где он отслеживает путь unc, и сеть, кажется, выходит из строя, что приводит к прекращению работы, поэтому я был уверен, что это будет хорошо.

Я думаю, FileSystemWatcher вызывает событие Changed в отдельном потоке (не проверено), так как мой сервис выполняет функцию Export() в потоке таймера, который будет заблокирован, пока не завершится выполнение макроса Excel.

Оказывается, служба успешно работает и отслеживает файл.

internal class myWorkbook : IDisposable
{
    private Microsoft.Office.Interop.Excel.Application app = null;
    private Microsoft.Office.Interop.Excel.Workbook myWorkbook = null;
    private string _myWorkbookUri;
    private FileSystemWatcher watcher;
                private bool _visible;
                private string _logFile;

    public myWorkbook(string myWorkbookUri, string)
    {
        _myWorkbookUri = myWorkbookUri;
    }

    public string Export(DateTime date)
    {
        app = new Microsoft.Office.Interop.Excel.Application();
        app.Visible = false;
        app.DisplayAlerts = false;
        app.Interactive = false;
        app.AskToUpdateLinks = false;
        myWorkbook = app.Workbooks.Open(_myWorkbookUri, 0, true, 5, "", "", false, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "", true, false, 0, true, true, false);
        CreateWatcher();

        return (string)app.Run("GenerateTextFile", date);       
    }


    private void CreateWatcher()
    {
        watcher = new FileSystemWatcher();
        watcher.Path = Path.GetDirectoryName(_logFile);
        watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        watcher.Filter = "*.txt"; // could use _logFile;
        watcher.Changed += watcher_Changed;
        watcher.EnableRaisingEvents = true;
    }

    void watcher_Changed(object sender, FileSystemEventArgs e)
    {
        if (e.FullPath == _logFile)
        {
            using (FileStream fs = new FileStream(_logFile,
                                  FileMode.Open,
                                  FileAccess.Read,
                                  FileShare.ReadWrite))
            {
                using (StreamReader sr = new StreamReader(fs))
                {
                    while (sr.Peek() >= 0)
                    {
                        var line = sr.ReadLine();
                        var tabs = line.Split(new char[] { '\t' });
                        DateTime eventTime = DateTime.Parse(tabs[0]);

                        if (DateTime.Compare(eventTime, lastEventTime) > 0)
                        {
                            string eventMessage = tabs[1];
                            OnProgressChanged(new ProgressChangedEventArgs(eventTime, eventMessage));

                            lastEventTime = eventTime;
                        }
                    }
                }
            }
        }
    }

}

Я делал это раньше. Создайте таблицу БД, в которую вы будете обновлять статус вашего долгосрочного задания. Ваше приложение отправляет запрос на запуск этой работы и должно создать Id для этой работы. Ваше приложение начинает отслеживать эту работу по таймеру. Ваш выигрышный сервис забирает эту работу и начинает обновлять вашу запись, и ваше приложение теперь будет показывать это обновление пользователю. Ваша служба win передает дескриптор автоматизации Excel, которая теперь отвечает за обновление длительной записи задания. Ваше клиентское приложение будет отражать изменения для пользователя. После завершения обработки ваш клиент должен действовать соответственно. Сообщение, всплывающее окно и т. Д. Уведомят пользователя о выполненной или неудачной работе.

Также многие разработчики создают сервис win, который постоянно работает по таймеру. Один из способов сделать это более эффективным - создать слушателя. Таким образом, служба win прослушивает какой-либо TCP-порт, и ваше приложение подает сигнал на этот порт. Win сервис проснется и начнет обрабатывать задания.

Другие вопросы по тегам