我有一个在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中移出任何东西。
答案 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;
}
}
}
}
}
}
}