- PR -

またまたEXCELのプロセスについて

1
投稿者投稿内容
みつん
大ベテラン
会議室デビュー日: 2004/05/21
投稿数: 100
投稿日時: 2006-06-03 22:40
OS:Windows2003Server
OfficeXP

度々で申し訳ございませんが、また気になる点が。
こちらの掲示板のアドバイスにより、ASP.NETにてEXCELを出力しプロセスも残らず
消えるようになりましてはや一年。
去年皆様のお手を借りながら作り上げたのは、WebサーバーがWindows2000でした。

現在Windows2003サーバーにて同じようなWebアプリを開発しております。
もう既存の技術なので安心していたのですが、全く同じコードで同じ設定
(dcomcnfigでの設定等)で記述していてもIUSERのEXCELプロセスが消えません!

調べますと、別の掲示板ですが同様な事が書かれてましたが解決には至ってませんでした。試しに2000サーバーでプロセスがきれいに消えるバージョンを2003サーバーにセットアップしてみたのですが、やはり消えなかったのでコードの問題ではないと思います。(多分・・・)


何かWindows2000とは別の設定があるのでしょうか?
どなたかお気づきの点がありましたら、ぜひアドバイスお願いいたします。
(ほんと、プロセスネタばかりですいません・・・。)

かるあ
ぬし
会議室デビュー日: 2003/11/16
投稿数: 1190
お住まい・勤務地: センガワ→ムサシノ
投稿日時: 2006-06-04 00:00
同じコードとはどのようなコードですか?
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-06-04 10:27
引用:

みつんさんの書き込み (2006-06-03 22:40) より:

調べますと、別の掲示板ですが同様な事が書かれてましたが解決には至ってませんでした。試しに2000サーバーでプロセスがきれいに消えるバージョンを2003サーバーにセットアップしてみたのですが、やはり消えなかったのでコードの問題ではないと思います。


(Windows というより、IIS のせいのような気がしますが)

どこかで似たような質問を見かけて回答したことがありますが、コードが原因でした。
もともとのコードで解放漏れがあったのですが、Windows 2000 Server では、問題なく解放されていたようです。
Windows Server 2003 になって、コード上の解放漏れの影響が出てきたというものでした。

いずれにせよ、一連のミニマム コードを提示して頂ければ見ることはできます。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
みつん
大ベテラン
会議室デビュー日: 2004/05/21
投稿数: 100
投稿日時: 2006-06-04 15:07
かるあさん、じゃんぬねっとさん、いつもありがとうございます!

そっか、そもそも開放漏れがあったのにもかかわらず
うまくいっていただけかもしれないですね。
ということで、お言葉に甘えてコードを載せさせていただきます。

---------------------------------------------------------------
private Excel.Application app;
private Excel.Workbooks books;
private Excel.Workbook book;
private Excel.Sheets sheets;
private Excel.Worksheet sheet;

private void Page_Load(object sender, System.EventArgs e)
{
try
{
// エクセルオブジェクト生成
app = new Excel.Application();

// テンプレートをコピーし新規ブックを作成
books = app.Workbooks;
book = books.Add(templeteFileName);
sheets = book.Worksheets;
sheet = (Excel.Worksheet)sheets[1];

// 取得データをシートにマッピング
sheet.Cells[2, 1] = 1
sheet.Cells[2, 4] = "テスト";

// テンポラリファイル名をメンバ変数にセット
string tmpFileName = Application["ExcelFilePath"].ToString() +
context.UserId + DateTime.Now.ToString("yyyyMMdd") +
DateTime.Now.Millisecond.ToString() + ".xls";

// テンポラリファイル作成
book.SaveCopyAs(tmpFileName);
// オブジェクトの開放
app.DisplayAlerts = false;
app.Quit();

if(sheets != null)Marshal.ReleaseComObject(sheets);
if(sheet != null)Marshal.ReleaseComObject(sheet);
if(books != null)Marshal.ReleaseComObject(books);
if(book != null)Marshal.ReleaseComObject(book);
if(app != null)Marshal.ReleaseComObject(app);
System.GC.Collect();

System.IO.FileInfo fileInfo = new System.IO.FileInfo(tmpFileName);
long nSize = fileInfo.Length;
string cvtFullPath = System.Web.HttpUtility.UrlEncode(tmpFileName);
Response.Clear();
Response.ContentEncoding = System.Text.Encoding.GetEncoding("shift-jis");
Response.ContentType = "application/octet-stream-dummy";
Response.AppendHeader("Content-Disposition", "attachment;
filename="+ "MBOSheet.xls");
Response.WriteFile(tmpFileName, 0, nSize);
Response.Flush();
Response.Close();

// テンポラリファイルの削除
System.IO.File.Delete(tmpFileName);
}
catch(Exception ex)
{
app.Quit();
if(sheets != null)Marshal.ReleaseComObject(sheets);
if(sheet != null)Marshal.ReleaseComObject(sheet);
if(books != null)Marshal.ReleaseComObject(books);
if(book != null)Marshal.ReleaseComObject(book);
if(app != null)Marshal.ReleaseComObject(app);
// テンポラリファイルの削除
System.IO.File.Delete(tmpFileName);
System.GC.Collect();
}
finally
{
Response.End();
}
}

----------------------------------------------------------------------

ミニマムと言われつつ、たくさん書いてしまいました・・・。
既存のEXCELテンプレートにデータのみマップしたいのでこのような方式にしています。

やっぱり開放漏れあるのかな・・・。
宜しくお願いいたします。

ちなみにガベージコレクションのせいか、
しばらくするとプロセスは消えていました。




じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-06-04 16:17
引用:

みつんさんの書き込み (2006-06-04 15:07) より:

// 取得データをシートにマッピング
sheet.Cells[2, 1] = 1
sheet.Cells[2, 4] = "テスト";


まず、ここに Excel.Range オブジェクトへの暗黙の参照があります。

引用:

app.Quit();


Excel.Application を Quit するのであれば、その前に解放すべきものがあります。
それが以下のものですが─

引用:

if(sheets != null)Marshal.ReleaseComObject(sheets);
if(sheet != null)Marshal.ReleaseComObject(sheet);
if(books != null)Marshal.ReleaseComObject(books);
if(book != null)Marshal.ReleaseComObject(book);
if(app != null)Marshal.ReleaseComObject(app);


─ただ、ここに順番の誤りがあります。
Worksheet -> Sheets -> Workbook -> Workbooks -> Application という順番に解放します。
(順番どおりでなくとも解放されることはあります)

また、Excel.Application を Quit するのであれば、
開いている Workbook を、手動で Close する必要があります。

解放に関する順番は、こちらが参考になるかと思います。(ネストされた順番です)

  COM オブジェクトを解放する

引用:

System.GC.Collect();


コストが高いので使用しない方が良いです。

それと、このタイミングでの Collect は、COM ラッパ オブジェクト自体の参照が残っているので意味がありません。
こちらに関しての有効性は、このあたりに書いています。

  COM 参照を確実に解放するコードの可読性をあげたい

このように未到達にするか、Null 参照にしておく必要があります。
ただし、これはただの実験ですから、絶対に参考にしないでください

# 長文 && 拙い説明になってしまいましたが、お許しください。(*_ _)

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
みつん
大ベテラン
会議室デビュー日: 2004/05/21
投稿数: 100
投稿日時: 2006-06-04 17:54
じゃんぬさん!じゃんぬさん!とっても詳しくありがとうございます!
そして、返信してくれて本当にありがとうございます!!

Cellsを宣言に作り、一旦参照を入れるように修正しました。

private Excel.Range cells;

cells = sheet.Cells;

そうすると見事にきれいにプロセスが消えました!
実はそもそものコードにプロセス残りが多々あるとの事で、GCをコメントアウトにして
WindowsXPの開発環境で動かしてみたんです。
そうするとプロセス、消えなかったんです・・・。
じゃんぬさんのアドバイス通り開放の順番を見直して、rangeオブジェクトも開放するように追加すると、GCがなくてもきちんと消えるようになりました。
これは本当に本当に大事なことです。
そもそも恐ろしいコードになっていたのですから。

過去のコードも見直せて本当に感謝しております。
心からありがとうございました


じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2006-06-04 20:29
解決できたようで、良かったです。

引用:

みつんさんの書き込み (2006-06-04 17:54) より:

private Excel.Range cells;
cells = sheet.Cells;


できれば、ローカル変数でかつ、局所的な変数を使うようにしてください。

Excel.Range (Cells) オブジェクトは、新しいインスタンスを上書きする前に、
明示的に参照カウントをデクリメントしないと、上書きされたインスタンスが未到達になります。
(Excel.Range (Cells) オブジェクトだけではないのですが、上書きされることが多いので)

それを防ぐためにも、局所的な変数を使って、可読性を良くし、
コーディング ミスを防ぐようにするのが鉄則だと思います。

局所的な変数にする理由はもう 1 つあります。(多分、こちらの方が重要です)

局所的な変数にすることで、COM 'ラッパ' オブジェクトが未到達になりやすいです。
万が一の時も、ガベージ コレクションによる解放が期待できます。

引用:

private Excel.Application app;
private Excel.Workbooks books;
private Excel.Workbook book;
private Excel.Sheets sheets;
private Excel.Worksheet sheet;


このあたりに関しても同様なことが言えます。
どうしても、private なアクセシビリティにしたい場合は、
この一連の処理をクラスに包容するようにします。

何故、ここまで慎重さを追求するかと言いますと、
ASP.NET (Web アプリケーション) の場合、プロセスが居残ると致命的だからです。

Windows アプリケーションの場合は、影響を受ける端末は少ないです。(通常 1 台)
しかし、Web アプリケーションの場合は、あまりに影響が大きいです。
また、復旧によるコスト (保守コスト) も考えると、慎重すぎるくらいが丁度良いわけです。

ですので、try ~ finally を使用して、finally で必ず COM 参照を解放することに加え、
'例外発生時' は、COM 'ラッパ' オブジェクトが未到達になった段階で、
GC.Collect メソッドを呼び出し、回収を依頼することを推奨します。

コストが高いので、'正常時' は、GC.Collect メソッドを呼び出してはいけません。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
1

スキルアップ/キャリアアップ(JOB@IT)