1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WinFormsで「別のプロセスが使用中です」の原因になりやすい所|Stream・ファイル出力・ログ・別処理【掟R11】

1
Posted at

連載Index(読む順・公開済リンクが最新): S00_門前の誓い_総合Index

保存したあとに上書きできない。削除できない。リネームできない。
WinForms では、別のプロセスが使用中です が保存や削除の行で出ていても、原因はその行より前にあることがあります。

多いのは、Stream を閉じていない、出力に使ったファイルの閉じ方が足りない、ログファイルを長く開いたままにしている、別の場所でも同じファイルを書いている、の 4 つです。
このページでは、保存や削除で止まる時に確認したい所を 4 つに分けて見ていきます。

保存処理そのものが悪いとは限りません。
少し前に開いたままの Stream、終わっていないファイル出力、開いたままのログ、別の場所からの書き込みが残っていると、次の保存や削除で止まりやすくなります。
.NET 8 を前提にしていますが、考え方は .NET Framework 4.8.1 でもほぼ同じです。

1. 保存や削除で止まる時の確認表

困り方 まず確認する場所 先に直す所
読み込んだあと削除できない FileStream / StreamReader そのメソッドの中で閉じる
出力したファイルをあとで触れない 出力コード 使った方式ごとに後始末を書く
ログがたまに失敗する StreamWriter の持ち方 長く持たず、書いたら閉じる
保存で急に止まる Task / タイマー / 別画面 同じファイルを書く場所を絞る

保存で止まっていても、原因は保存処理そのものとは限りません。
少し前に開いた Stream や、別の場所で続いている書き込み処理が原因になっていることもあります。

2. 読み込みや保存のあと触れない時は Stream を確認する

いちばん多いのはここです。
WinForms では、ボタンクリックの中に読込や保存をそのまま書くことが多いので、閉じる行が抜けやすくなります。

悪い例

private void btnLoad_Click(object sender, EventArgs e)
{
    var stream = File.OpenRead(txtPath.Text);
    var reader = new StreamReader(stream);

    txtBody.Text = reader.ReadToEnd();
}

この形だと、閉じる行がありません。
途中で return が入っても同じです。

直した例

private void btnLoad_Click(object sender, EventArgs e)
{
    // このメソッドの中だけで使う
    using var stream = File.OpenRead(txtPath.Text);
    using var reader = new StreamReader(stream);

    txtBody.Text = reader.ReadToEnd();
}

保存側も同じです。

private void btnSave_Click(object sender, EventArgs e)
{
    // 書き込みが終わったらその場で閉じる
    using var stream = File.Create(txtPath.Text);
    using var writer = new StreamWriter(stream);

    writer.Write(txtBody.Text);
}

ここで確認するのは、このメソッドを抜けたあとにも必要か だけです。
必要ないなら、その場で閉じます。
その方が、あとで確認しやすくなります。

File.OpenRead()new StreamReader() が見つかったのに、閉じる行が見当たらない時はここから見直します。

3. 出力したファイルをあとで触れない時は、使った方式ごとの後始末を確認する

ファイル出力は、使う仕組みで閉じ方が変わります。
ここを一緒に考えると、どこを直すかが見えにくくなります。

ここでいうファイル出力には、Excel、CSV、TXT などが含まれます。
先に確認するのは、どの方式で出しているか です。

ClosedXML / EPPlus のような方式

この系統は、そのメソッドの中で作って閉じる形が基本です。

using ClosedXML.Excel;

private void ExportByClosedXml(string path)
{
    // workbook はこのメソッドの中で閉じる
    using var workbook = new XLWorkbook();
    var sheet = workbook.Worksheets.Add("出力");

    sheet.Cell(1, 1).Value = "商品";
    sheet.Cell(1, 2).Value = "金額";

    workbook.SaveAs(path);
}

Excel Interop のような方式

Interop は Close() だけで終わったつもりになりやすいです。
参照が残ると、Excel プロセスも残りやすくなります。

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;

private void ExportByInterop(string path)
{
    Excel.Application? app = null;
    Excel.Workbooks? books = null;
    Excel.Workbook? book = null;
    Excel.Worksheet? sheet = null;

    try
    {
        app = new Excel.Application();
        books = app.Workbooks;
        book = books.Add();
        sheet = (Excel.Worksheet)book.Worksheets[1];

        sheet.Cells[1, 1] = "商品";
        sheet.Cells[1, 2] = "金額";

        book.SaveAs(path);
    }
    finally
    {
        if (book is not null)
        {
            book.Close(false);
        }

        if (sheet is not null) Marshal.ReleaseComObject(sheet);
        if (book is not null) Marshal.ReleaseComObject(book);
        if (books is not null) Marshal.ReleaseComObject(books);

        if (app is not null)
        {
            app.Quit();
            Marshal.ReleaseComObject(app);
        }
    }
}

方式が違うと、閉じ方も変わります。
出力コードを見る時は、まずそこから分けた方が早いです。

出力したファイルをあとで触れない時は、先に方式を見ます。
ClosedXML / EPPlus と Interop では、確認する場所が違います。

4. ログがたまに失敗する時は StreamWriter の持ち方を確認する

CSV、TXT、ログは軽く見えますが、ここでも止まりやすいです。
特に StreamWriter をフィールドで持ち続ける形は、原因になっていないか先に確認したい所です。

悪い例

private StreamWriter? _writer;

private void StartLog(string path)
{
    _writer = new StreamWriter(path, append: true);
}

private void WriteLog(string message)
{
    _writer?.WriteLine(message);
}

この形だと、画面が開いている間ずっとログファイルを開いたままにしやすくなります。

直した例

private void WriteLog(string path, string message)
{
    // 書いたら閉じる
    using var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
    using var writer = new StreamWriter(stream);

    writer.WriteLine(message);
}

回数がそれほど多くないなら、この形の方が単純です。
まずは開いたままにしない書き方へ変えた方が早いです。

長く持つなら、閉じる場所まで書く

private StreamWriter? _writer;

private void StartLog(string path)
{
    var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
    _writer = new StreamWriter(stream) { AutoFlush = true };
}

protected override void OnFormClosed(FormClosedEventArgs e)
{
    _writer?.Dispose();
    _writer = null;

    base.OnFormClosed(e);
}

ここでは、どこで作るか だけでなく どこで閉じるか まで見えることが大事です。

5. 保存で急に止まる時は別の場所からの書き込みも確認する

保存ボタンのコードだけ見ても直らない時があります。
その時は、別の場所でも同じファイルを書いていないかを確認します。

よくあるのは次です。

  • Task.Run 側でも書いている
  • タイマーでも同じログを書いている
  • 別画面でも同じ CSV を触っている
  • 監視処理の直後に自分でも書いている

見直し例

private readonly SemaphoreSlim _saveGate = new(1, 1);

private async Task SaveTextAsync(string path, string text)
{
    await _saveGate.WaitAsync();

    try
    {
        // 書き込みを 1 本にする
        await File.WriteAllTextAsync(path, text);
    }
    finally
    {
        _saveGate.Release();
    }
}

ここで見るのは、同じファイルへ書く場所が何か所あるか です。
複数の場所から書ける形だと、失敗した時に確認する場所が増えます。

「保存で止まるから保存処理だけ見る」で終わると、原因を見落としやすくなります。
同じファイルに触っている別処理がないかも見た方が早いです。

6. 4か所をまとめて確認する表

確認する場所 ありがちな書き方 先に直す所
Stream 開くだけで閉じる行がない そのメソッドの中で閉じる
ファイル出力 方式ごとの終わりが書かれていない 使った方式ごとに後始末を書く
ログ StreamWriter を長く持つ 書いたら閉じる。長く持つなら終了位置も書く
別処理 同じファイルを書く場所が複数ある 書き込み場所を絞る

7. 確認する順番

  1. Stream をそのメソッドの中で閉じているか
  2. ファイル出力の方式ごとの後始末が書かれているか
  3. ログを長く持ちすぎていないか
  4. 同じファイルを書く場所が複数ないか

別のプロセスが使用中です が出た時は、エラーが出た行だけを見ても足りないことがあります。
少し前に開いた Stream や、別の場所で続いている書き込みまで見ると、原因が見つかりやすくなります。

関連リンク

連載Index(読む順・公開済リンクが最新): S00_門前の誓い_総合Index

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?