C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ツリー一括表示

FormClosingでセーブするが稀に最後まで書込まれない /Tom (26/05/05(Tue) 11:26) #103927
Re[1]: FormClosingでセーブするが稀に最後まで書込まれない /魔界の仮面弁士 (26/05/05(Tue) 17:57) #103929
  └ Re[2]: FormClosingでセーブするが稀に最後まで書込まれない /魔界の仮面弁士 (26/05/05(Tue) 18:53) #103930
    └ Re[3]: FormClosingでセーブするが稀に最後まで書込まれない /Tom (26/05/06(Wed) 13:09) #103931
      └ Re[4]: FormClosingでセーブするが稀に最後まで書込まれない /Tom (26/05/13(Wed) 20:50) #103933
        └ Re[5]: FormClosingでセーブするが稀に最後まで書込まれない /Azulean (26/05/13(Wed) 22:06) #103934
          └ Re[6]: FormClosingでセーブするが稀に最後まで書込まれない /魔界の仮面弁士 (26/05/14(Thu) 11:41) #103935


親記事 / ▼[ 103929 ]
■103927 / 親階層)  FormClosingでセーブするが稀に最後まで書込まれない
□投稿者/ Tom (1回)-(2026/05/05(Tue) 11:26:47)

分類:[C#] 

開発環境VisualStudio2022
実行環境Windows10

FormClosingイベントでデータをテキスト形式で保存しています。
その時、「稀に」最後まで書き込まれないときがあります。
イメージとしてはこんな感じ

 あいうえおかきくけこさしすせそたちつてと
 あいうえおかきくけこさしすせそたちつてと
 あいうえおかきくけこさしすせそたちつてと
 あいうえおかきくけこさしすせそ[EOF]

コードは単純で下記の感じです。
System.IO.StreamWriter st = new System.IO.StreamWriter(csvFileName, false, System.Text.Encoding.GetEncoding("Shift_JIS"));
for(int cnt=0;cnt<件数;cnt++){
st.Write("あいうえおかきくけこさしすせそたちつてと\r\n"); ←実際の書き込む内容は多いもので1行2000文字以上です。
}
st.Close();

ただ件数がいま50万行をこえており書き込みに数秒かかります。
メインフォームのFormClosingイベントがセーブのトリガーだから、もしかして最後まで書き終わる前にプロセスおわっちゃう?なんて思ったのですが、この50万件のデータを書き込む関数のあとに数十行のデータを書き込む関数を呼んでいるのですがそっちは何の問題もなく書き込まれていました。

賢者の方、原因と対策がお分かりの方、アドバイスいただけませんでしょうか?
[ □ Tree ] 返信 編集キー/

▲[ 103927 ] / ▼[ 103930 ]
■103929 / 1階層)  Re[1]: FormClosingでセーブするが稀に最後まで書込まれない
□投稿者/ 魔界の仮面弁士 (3910回)-(2026/05/05(Tue) 17:57:18)
No103927 (Tom さん) に返信
> ただ件数がいま50万行をこえており書き込みに数秒かかります。

1行2000文字超 × 50万行だと、最大で約10億文字(2GB弱)換算ですね?

> この50万件のデータを書き込む関数のあとに数十行のデータを書き込む関数を呼んでいるのです
同一ファイルへの追記 (上書きではない) と思ってよいでしょうか。

それぞれの関数が、同一の Stream インスタンスに対して書き込んでいるのか、
あるいはファイル名のみが共有されており、それぞれの関数内で
書き込んでは都度 Close している処理なのかでも話が変わってきそうですが。


> もしかして最後まで書き終わる前にプロセスおわっちゃう?なんて思ったのですが、
OS の Write Cache 書き込みの遅延が原因では無いでしょうか。

通常、StreamWriter.Flush() や Close() を呼んだだけでは、データは
.NET のバッファから OS のバッファに移るだけで、そこから
物理ディスクへの書き込み完了については OS のタイミングに委ねられます。

まずは FileStream.Flush(true) を試してみてください。
これは FlushFileBuffers API に相当する機能で、OS に対して
「ディスク装置のキャッシュもフラッシュせよ」という指示を行うものです。

using (var fs = new FileStream(csvFileName, FileMode.Create, FileAccess.Write))
using (var st = new StreamWriter(fs, System.Text.Encoding.GetEncoding("Shift_JIS")))
{
  //
  // 元の書き込み処理をここに記述
  //
  st.Flush(); // StreamWriter(アプリ層) のバッファを FileStream (OS層) へ
  fs.Flush(true); // OS のバッファを物理ディスクへ強制フラッシュ
}

この時、Flush(true) の前に Flush() を先に呼ぶようにしないと、
StreamWriter 内に残っているデータが OS に渡されないのでご注意を。

なお、Close() メソッドは内部で Flush() を呼びますが、
これは Flush(true) の呼び出しを含みません。
そのため、Flush(true) は明示的な呼び出しが必要です。
https://learn.microsoft.com/ja-jp/dotnet/api/system.io.filestream.flush?WT.mc_id=DT-MVP-8907&view=netframework-4.8.1
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/IO/StreamWriter.cs#L195


別案として…そもそも Write Cache を使わないという選択肢もあります。
FileOptions.WriteThrough を指定することで、Write Cache 無しで
直接ディスクに書き込める方法 (Unbuffered I/O) です。

ただし書き込んでいる間、システム全体のパフォーマンスが低下する可能性があります。
2GB 弱の書き込みにこれを使うと、数秒どころか分単位で待たされるかもしれません。

この機能はログやトランザクションなど、
データの整合性が速度よりも優先される場合に用いられるオプションです。

using (var fs = new FileStream(csvFileName, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
using (var st = new StreamWriter(fs, System.Text.Encoding.GetEncoding("Shift_JIS")))
{
  // 書き込み処理
}
[ 親 103927 / □ Tree ] 返信 編集キー/

▲[ 103929 ] / ▼[ 103931 ]
■103930 / 2階層)  Re[2]: FormClosingでセーブするが稀に最後まで書込まれない
□投稿者/ 魔界の仮面弁士 (3911回)-(2026/05/05(Tue) 18:53:27)
No103929 (魔界の仮面弁士) に追記
> なお、Close() メソッドは内部で Flush() を呼びますが、
> これは Flush(true) の呼び出しを含みません。
> そのため、Flush(true) は明示的な呼び出しが必要です。
> https://learn.microsoft.com/ja-jp/dotnet/api/system.io.filestream.flush?WT.mc_id=DT-MVP-8907&view=netframework-4.8.1
> https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/IO/StreamWriter.cs#L195

失礼しました、上記に記載の github.com/dotnet は「.NET」のソースでしたね。
それに、FileStream.Flush を紹介するべきところで、StreamWriter.Flush ソースへのリンクになってしまっていました。

「.NET Framework」の FileStream.Flush のソースはこっち。
https://github.com/microsoft/referencesource/blob/main/mscorlib/system/io/filestream.cs#L1322

「public virtual void Flush(Boolean flushToDisk)」において、
引数が true の時に FlushOSBuffer(); が呼ばれているけれど、
Close 等から呼ばれているのは、Flush(false) であることが分かります。
[ 親 103927 / □ Tree ] 返信 編集キー/

▲[ 103930 ] / ▼[ 103933 ]
■103931 / 3階層)  Re[3]: FormClosingでセーブするが稀に最後まで書込まれない
□投稿者/ Tom (2回)-(2026/05/06(Wed) 13:09:45)
魔界の仮面弁士さま
詳細なアドバイス、ありがとうございます。

2つめの関数での数十行の出力は成功していたので、Flush()は関係ないか とおもって試しても居ませんでした。
それにFileStreamとStreamWriterの2つのFlush()があるとは知りませんでした。
早速実装してみます。
ただ、初めに記載した通り「稀に」しか発生せず、しかも状況からお察しの通りOSの書き込みタイミングが絡んでくるので再現性が皆無です。
実装後しばらく(もしかすると数か月のスパン?)で再度発生しないなら「対策は正解だった」という判断なのでもしかすると忘れたころにまたお聞きするかもしれません。
その時はまたご助言ください。
本日はありがとうございました。

[ 親 103927 / □ Tree ] 返信 編集キー/

▲[ 103931 ] / ▼[ 103934 ]
■103933 / 4階層)  Re[4]: FormClosingでセーブするが稀に最後まで書込まれない
□投稿者/ Tom (3回)-(2026/05/13(Wed) 20:50:58)
うう、また発生しました・・・
まだなにか問題があるようです。
フォームクローズ(×ボタン押下)でメイン画面をハイドして見た目はすぐに終わったようなふりをしているのが不味いのかなぁ

なにかこの辺を探ってみろ というアドバイスを頂けると幸いです。
[ 親 103927 / □ Tree ] 返信 編集キー/

▲[ 103933 ] / ▼[ 103935 ]
■103934 / 5階層)  Re[5]: FormClosingでセーブするが稀に最後まで書込まれない
□投稿者/ Azulean (1回)-(2026/05/13(Wed) 22:06:54)
2026/05/14(Thu) 07:24:31 編集(投稿者)

直接の回答ではありません。

・FormClosing はキャンセル確認をするためのタイミングであり、終了は確定していない。
 →たとえば、Windowsシャットダウンの場合、FormClosingの後、他のアプリでシャットダウンをキャンセルしたら、「閉じません」(FormClosedが発生しない)。

・Windows Vista 以降では、FormClosing/FormClosed で長時間の処理をすることは非推奨になっています。
 →終了時に時間のかかる仕様・設計を避けるべきだと Microsoft は言っています。

・シャットダウン契機で発せられる FormClosing は 5 秒以内の完了が求められるはず。(超過した場合は、シャットダウンを妨げていますと出るなど、正常終了されない可能性もある)
 https://learn.microsoft.com/ja-jp/windows/win32/shutdown/wm-queryendsession#remarks

(参考)
https://learn.microsoft.com/ja-jp/windows/win32/shutdown/shutting-down
> アプリケーションでは、データと状態を頻繁に保存することをお勧めします。たとえば、ユーザーが開始した保存操作の間にデータを自動的に保存して、シャットダウン時に保存するデータの量を減らします。


このような状況にあるため、FormClosing で長時間かかるというシナリオ自体を避けることが多く、どうして起こるのか? どうしたら軽減できるのか? の知見は少ないかもしれません。
[ 親 103927 / □ Tree ] 返信 編集キー/

▲[ 103934 ] / 返信無し
■103935 / 6階層)  Re[6]: FormClosingでセーブするが稀に最後まで書込まれない
□投稿者/ 魔界の仮面弁士 (3912回)-(2026/05/14(Thu) 11:41:03)
No103934 (Azulean さん) に返信
> ・FormClosing はキャンセル確認をするためのタイミングであり、終了は確定していない。
>  →たとえば、Windowsシャットダウンの場合、FormClosingの後、他のアプリでシャットダウンをキャンセルしたら、「閉じません」(FormClosedが発生しない)。
UserClosing 等が契機なら、いったんキャンセルしておいて保存完了後に再終了する手もありますが、
TaskManagerClosing や WindowsShutDown などを考えると、望ましくは無いでしょうね。


>>アプリケーションでは、データと状態を頻繁に保存することをお勧めします。たとえば、ユーザーが開始した保存操作の間にデータを自動的に保存して、シャットダウン時に保存するデータの量を減らします。
新規ファイルではなく上書き保存が必要な場合、アイドル時には別ファイルに残しておいて、
終了時にそれを File.Replace で入れ替えるパターンもありますね。
既に作成しておいたファイルとの置換だけなら短い時間で終わるので。
(File.Replace での置換は atomic に行われるため、破損の危険性が抑えられる)


No103933 (Tom さん) に返信
> フォームクローズ(×ボタン押下)でメイン画面をハイドして見た目はすぐに終わったようなふりをしているのが不味いのかなぁ
Stream はいつ開いて、いつ閉じていますか?
頻繁に開いたり閉じたりを繰り返しているのか、
それともアプリが起動している間、共有あるいは排他で開き続けているのか。

FormClosing 以外の箇所では一切操作されないファイルなのでしょうか?
それとも、Form 内の他の箇所でも使われているファイルでしょうか?

頻繁に開いたり閉じたりを繰り返しているのであれば、編集モードでファイルを開き続けておき、
「50万件出力」と「数十行出力」部が、同一の Stream インスタンスに対して
処理されるようにすれば、50万部だけが欠損するようなことを防げる気がします。
同一スレッドからの出力であるならば。
[ 親 103927 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -