0

私は、C# で Excel ワークブックを自動化するという最も恐ろしい仕事をしています。プロセスの一部は、実行に約 2 分かかるブック内のマクロを呼び出すことです。これはすべて Windows サービスで発生し、最初から最後まで問題なく実行されます。現在、マクロを呼び出す直前とマクロの実行が終了したときに、イベントをデータベース テーブルに書き込みます。マクロ コード内で多くの計算を行い、データをテキスト ファイルにエクスポートします。

非常に時間がかかるため、プロセスのさまざまな部分で通知を受け取ることができるかどうか、ユーザーから問い合わせがありました。

予約に関する私の最初の考えは、System.Timers.Timer を使用してマクロを実行している間に更新される Application.StatusBar を定期的にポーリングすることでした。これには何らかのスレッドの問題があるのではないかと思いました.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 から何かを移動する余地はありません。

4

2 に答える 2

1

私は前にこれをやったことがあります。長時間実行ジョブのステータスを更新する DB テーブルを作成します。アプリケーションは、このジョブを開始するリクエストを送信し、このジョブの ID を作成する必要があります。アプリケーションは、タイマーでこのジョブの監視を開始します。win サービスがこのジョブを受け取り、レコードの更新を開始すると、アプリケーションはこの更新をユーザーに表示します。Win サービスはハンドルを Excel オートメーションに渡します。Excel オートメーションは、実行時間の長いジョブ レコードの更新を担当します。クライアント アプリはユーザーへの変更を反映します。処理が完了すると、クライアントはそれに応じて行動する必要があります。メッセージやポップアップなどで、完了または失敗したジョブがユーザーに通知されます。

また、多くの開発者は、タイマーで常に実行される win サービスを作成しています。より効率的に行う 1 つの方法は、リスナーを作成することです。したがって、勝利サービスはいくつかの TCP ポートをリッスンし、アプリはそのポートに信号を送ります。Win サービスが起動し、ジョブの処理を開始します。

于 2013-08-06T14:56:15.657 に答える
0

基本的に、ワークブックを少し変更して、テキスト ファイルにログを記録しました。テキスト ファイルは、FileSystemWatcher を使用して私のプロセスによって監視されました。

ワークブックは、ログ ファイルをサーバー上にローカルに作成します。過去にFileSystemWatcherでUNCパスを監視し、ネットワークがダウンして動作が停止するという問題が発生したため、これで問題ないと確信していました。

私のサービスは、Excel マクロの実行が完了するまでブロックされるタイマー スレッドで Export() 関数を実行するため、FileSystemWatcher は別のスレッド (検証されていない) で Changed イベントを発生させると思います。

結局のところ、サービスは正常に実行され、ファイルを監視しています。

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

}
于 2013-08-09T07:33:09.010 に答える