Excel自动化的反馈和长时间运行的宏的执行

时间:2013-08-06 13:51:42

标签: .net feedback excel-automation

我有一个在C#中自动化excel工作簿的最糟糕的任务。部分过程是调用工作簿中的宏,大约需要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 个答案:

答案 0 :(得分:1)

我以前做过这个。创建一个DB表,以更新长时间运行的作业状态。您的应用程序发送启动此作业的请求,并应为此作业创建Id。您的应用程序开始在计时器上监视此作业。您的获胜服务将接受此工作并开始更新您的记录,您的应用程序现在将向用户显示此更新。您的win服务将句柄传递给Excel自动化,后者现在负责更新长时间运行的作业记录。您的客户端应用程序将反映用户的更改。处理完成后,您的客户应该采取相应的行动。消息,弹出窗口等将通知用户已完成或失败的作业。

此外,许多开发人员创建了不断在计时器上运行的win服务。提高效率的一种方法是创建一个监听器。因此,win服务将侦听某个TCP端口,您的应用程序将向该端口发出信号。 Win服务将唤醒并开始处理作业。

答案 1 :(得分:0)

基本上我稍微修改了工作簿以登录到文本文件。然后,我的进程使用FileSystemWatcher监视文本文件。

工作簿在服务器上本地创建日志文件。我过去曾遇到过FileSystemWatcher的问题,它监视一条路径,网络似乎停止工作,导致它停止工作,所以我确信这没问题。

我认为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;
                        }
                    }
                }
            }
        }
    }

}