Directory.Delete(path、true)ではディレクトリを削除できません


383

私は.NET 3.5を使用しており、次を使用してディレクトリを再帰的に削除しようとしています。

Directory.Delete(myPath, true);

私の理解では、ファイルが使用中の場合やアクセス権の問題がある場合にスローされますが、それ以外の場合は、ディレクトリとそのすべてのコンテンツが削除されます。

しかし、私は時々これを手に入れます:

System.IO.IOException: The directory is not empty.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.Directory.DeleteHelper(String fullPath, String userPath, Boolean recursive)
    at System.IO.Directory.Delete(String fullPath, String userPath, Boolean recursive)
    ...

メソッドが時々スローすることには驚きませんが、再帰がtrueのときにこの特定のメッセージが表示されるのには驚きます。(知っているはディレクトリが空ではないています。)

AccessViolationExceptionの代わりにこれが表示される理由はありますか?


13
AccessViolationExceptionは表示されません-これは、ディスクアクセスではなく、無効なポインター操作用です。
ジョーホワイト

1
これは、開いているファイルハンドルなど、ディレクトリが空でないだけでなく、何らかのIOの問題のようです。再帰的な削除オプションを使用してから、IOExceptionのキャッチで、開いているファイルハンドルを検索して閉じ、再試行します。それについての議論は終わっここにあります: stackoverflow.com/questions/177146/...
ダンCsharpster

回答:


231

編集者注:この回答にはいくつかの有用な情報が含まれていますが、の動作については事実として正しくありませんDirectory.Delete。この回答に対するコメント、およびこの質問に対するその他の回答を読んでください。


以前この問題に遭遇しました。

問題の根本は、この関数がディレクトリ構造内にあるファイルを削除しないことです。したがって、ディレクトリ構造を削除する前に、ディレクトリ構造内のすべてのファイルを削除してからすべてのディレクトリを削除する関数を作成する必要があります。これは2番目のパラメーターに反することはわかっていますが、はるかに安全なアプローチです。さらに、ファイルを削除する直前に、ファイルから読み取り専用アクセス属性を削除することをお勧めします。それ以外の場合は、例外が発生します。

このコードをプロジェクトに組み込むだけです。

public static void DeleteDirectory(string target_dir)
{
    string[] files = Directory.GetFiles(target_dir);
    string[] dirs = Directory.GetDirectories(target_dir);

    foreach (string file in files)
    {
        File.SetAttributes(file, FileAttributes.Normal);
        File.Delete(file);
    }

    foreach (string dir in dirs)
    {
        DeleteDirectory(dir);
    }

    Directory.Delete(target_dir, false);
}

また、私のために私は個人的に誰かが上で、この関数を呼び出すしたいですので、削除することが許可されているマシンの領域に制限を加えますC:\WINDOWS (%WinDir%)C:\


117
これはナンセンスです。Directory.Delete(myPath、true)は、ディレクトリ構造内にあるすべてのファイルを削除するオーバーロードです。間違ったくなったら、ライアンSの答えを間違えてください。
Sig。トレランツァ

35
+1。Directory.Delete()はサブディレクトリ内のファイルを再帰的に削除しますが(再帰= trueです)、サブディレクトリまたはファイルの1つが読み取り専用の場合は "IOException:Directory is not empty"をスローします。したがって、このソリューションはDirectory.Delete()よりもうまく機能します
Anthony Brien

17
Directory.Delete(path, true)ファイルを削除しないステートメントは間違っています。MSDN msdn.microsoft.com/en-us/library/fxeahc5f.aspx
Konstantin Spirin

20
-1このアプローチの有効性が非常に疑わしいことを誰かが明確にマーカーを付けてください。場合はDirectory.Delete(string,bool)失敗し、何かがロックまたはmispermissionedと、このような問題のすべてのソリューションにはワンサイズフィットにありません。人々はその問題を自分たちの文脈で対処する必要があり、私たちはすべてのアイデアを(再試行と例外を飲み込むことで)問題を大きく投げ、良い結果を期待していることを期待しています。
Ruben Bartelink

37
削除するディレクトリに他のフォルダへのショートカット/シンボリックリンクがある場合、このアプローチに注意してください-予想以上に削除される可能性があります
Chanakya

182

ディレクトリを再帰的に削除しようとしていてa、ディレクトリa\bがエクスプローラーで開いている場合、bは削除されますが、「ディレクトリが空ではありません」というエラーが表示されますa移動して見ると、ます。アプリケーション(エクスプローラーを含む)の現在のディレクトリは、ディレクトリへのハンドルを保持します。あなたが呼び出すとDirectory.Delete(true)、それはボトムアップから削除しますb、そしてabがエクスプローラーで開いている場合、エクスプローラーはの削除を検出し、bディレクトリを上方に変更cd ..し、開いているハンドルをクリーンアップします。ファイルシステムは非同期で動作するため、Directory.Deleteエクスプローラーとの競合により操作が失敗します。

不完全なソリューション

Explorerがディレクトリハンドルを解放できるように現在のスレッドを中断するという考えで、私は最初に次のソリューションを投稿しました。

// incomplete!
try
{
    Directory.Delete(path, true);
}
catch (IOException)
{
    Thread.Sleep(0);
    Directory.Delete(path, true);
}

しかし、これは、オープンディレクトリが が削除するディレクトリの直接の子であるます。a\b\c\dがエクスプローラーで開いている場合a、これをで使用するdと、とを削除すると、この手法は失敗しcます。

やや良い解決策

このメソッドは、下位レベルのディレクトリの1つがエクスプローラーで開いている場合でも、深いディレクトリ構造の削除を処理します。

/// <summary>
/// Depth-first recursive delete, with handling for descendant 
/// directories open in Windows Explorer.
/// </summary>
public static void DeleteDirectory(string path)
{
    foreach (string directory in Directory.GetDirectories(path))
    {
        DeleteDirectory(directory);
    }

    try
    {
        Directory.Delete(path, true);
    }
    catch (IOException) 
    {
        Directory.Delete(path, true);
    }
    catch (UnauthorizedAccessException)
    {
        Directory.Delete(path, true);
    }
}

自分で再帰するという余分な作業にもかかわらず、私たちは まだ処理する必要がありますUnauthorizedAccessException道に沿って発生する可能性があることを。最初の削除の試みが2番目の成功の試みに道を開いているのか、それとも単に例外をスロー/キャッチすることによって引き起こされたタイミング遅延がファイルシステムに追いつくのかを明確にしていない。

を追加することで、一般的な条件下でスローおよびキャッチされる例外の数を減らすことができる場合があります。 Thread.Sleep(0)tryブロックの先頭にをあります。さらに、システムの負荷が高い場合、両方のDirectory.Delete試行を飛ばして失敗する可能性があります。このソリューションを、より堅牢な再帰的削除の開始点と考えてください。

一般的な答え

このソリューションは、Windowsエクスプローラーとのやり取りの特殊性のみを扱います。強固な削除操作が必要な場合は、いつでも(ウイルススキャナーなど)何でも削除しようとしているものへのオープンハンドルが存在する可能性があることに注意してください。したがって、後で再試行する必要があります。どれくらい後で、何回試すかは、オブジェクトを削除することがどれほど重要かによって異なります。MSDNを示し

堅牢なファイル反復コードは、ファイルシステムの多くの複雑さを考慮する必要があります。

NTFSのリファレンスドキュメントへのリンクのみが提供されているこの無実のステートメントは、あなたの髪を際立たせるはずです。

編集:たくさん。この答えは当初、最初の不完全な解しかありませんでした。)


11
Windowsエクスプローラーでパスまたはパスの下のフォルダー/ファイルの1つが開いているか選択されているときにDirectory.Delete(path、true)を呼び出すと表示され、IOExceptionがスローされます。Windowsエクスプローラーを閉じて、上記のtry / catchを使用せずに既存のコードを再実行すると、問題なく動作しました。
David Alpert

1
私はそれがどのようにどのように機能するかを理解することはできませんが、ファイル属性を設定して自分自身の再帰関数を作成している間は機能しました。
スティルガー

1
@CarlosLiu「エクスプローラーにディレクトリハンドルを解放する機会を与える」ため
Dmitry Gonchar

4
システムがエクスプローラに「ディレクトリハンドルを解放する」ように要求し、次にディレクトリを削除しようとしているのです。ディレクトリハンドルが時間内に削除されなかった場合は、例外が発生してcatchブロックが実行されます(その間、エクスプローラーはディレクトリを解放します。そうしないように指示するコマンドが送信されていないためです)。ブロックはすでにシステムにもう少し時間を与えているThread.Sleep(0)ため、への呼び出しは必要な場合と必要でない場合がありますがcatch、低コストで少し余分な安全性を提供します。その後、Deleteが呼び出され、ディレクトリはすでに解放されています。
ザカリークニーベル2013年

1
@PandaWoodは実際にはこのSleep(100)だけが私のために働きました。Sleep(0)は機能しませんでした。私は何が起こっているのか、これを適切に解決する方法がわかりません。つまり、サーバーの負荷に依存し、将来的には300または400になるとしたらどうでしょうか。それを知る方法。別の適切な方法でなければなりません...
ローマン

43

先に進む前に、自分の管理下にある次の理由を確認してください。

  • フォルダはプロセスの現在のディレクトリとして設定されていますか?はいの場合は、最初に別のものに変更します。
  • そのフォルダからファイルを開いた(またはDLLをロードした)か。(それを閉じる/アンロードするのを忘れた)

それ以外の場合は、制御できない次の正当な理由を確認してください。

  • そのフォルダーに読み取り専用としてマークされたファイルがあります。
  • これらのファイルの一部に対する削除権限がありません。
  • ファイルまたはサブフォルダーがエクスプローラーまたは別のアプリで開いています。

上記のいずれかに問題がある場合は、削除コードを改善する前に、その原因を理解する必要があります。アプリは読み取り専用またはアクセスできないファイルを削除する必要がありますか?誰がそのようにマークしたのか、そしてその理由は?

上記の理由を除外しても、偽の障害が発生する可能性があります。削除されるファイルまたはフォルダーのいずれかへのハンドルを保持している場合、削除は失敗します。フォルダーが列挙されたり、そのファイルが読み取られたりするのには、多くの理由があります。

  • 検索インデクサー
  • アンチウイルス
  • バックアップソフトウェア

偽の失敗に対処する一般的なアプローチは、複数回試行して、試行の合間に一時停止することです。あなたは明らかに永遠に試し続けることを望まないので、一定の試行の後にあきらめて、例外をスローするか、エラーを無視する必要があります。このような:

private static void DeleteRecursivelyWithMagicDust(string destinationDir) {
    const int magicDust = 10;
    for (var gnomes = 1; gnomes <= magicDust; gnomes++) {
        try {
            Directory.Delete(destinationDir, true);
        } catch (DirectoryNotFoundException) {
            return;  // good!
        } catch (IOException) { // System.IO.IOException: The directory is not empty
            System.Diagnostics.Debug.WriteLine("Gnomes prevent deletion of {0}! Applying magic dust, attempt #{1}.", destinationDir, gnomes);

            // see http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true for more magic
            Thread.Sleep(50);
            continue;
        }
        return;
    }
    // depending on your use case, consider throwing an exception here
}

私の意見では、偽のエラーが常に発生する可能性があるため、このようなヘルパーをすべての削除に使用する必要があります。ただし、このコードを単に盲目的にコピーするのではなく、ユースケースに適応させる必要があります。

%LocalAppData%の下にあるアプリによって生成された内部データフォルダーに誤ったエラーが発生したため、分析は次のようになります。

  1. フォルダーは私のアプリケーションによってのみ制御され、ユーザーはそのフォルダー内にあるものを読み取り専用またはアクセス不可としてマークする正当な理由がないので、そのような場合は処理しません。

  2. ユーザーが作成した貴重なものはありませんので、誤って何かを強制的に削除するリスクはありません。

  3. 内部データフォルダーなので、Explorerで開いているとは思いません。少なくとも、ケースを具体的に処理する必要性を感じていません(つまり、サポートを通じてそのケースを細かく処理しています)。

  4. すべての試みが失敗した場合、私はエラーを無視することを選択します。最悪の場合、アプリはいくつかの新しいリソースをアンパックできず、クラッシュし、ユーザーにサポートへの問い合わせを促しますが、それが頻繁に発生しない限り、私には問題ありません。または、アプリがクラッシュしない場合は、古いデータが残っているため、これも許容されます。

  5. 私は再試行を500ms(50 * 10)に制限することを選択します。これは、実際に機能する任意のしきい値です。私は、ユーザーが応答を停止したと考えてユーザーがアプリを強制終了しないように、しきい値を十分に短くしたいと考えました。一方、0.5秒は、犯罪者が私のフォルダの処理を完了するのに十分な時間です。他のSOの回答から判断して、それでもSleep(0)許容できる場合があるとすると、1回以上の再試行を経験するユーザーはほとんどいません。

  6. 私は50msごとに再試行しますが、これは別の任意の数値です。私がファイルを削除しようとしたときにファイルが処理(インデックス作成、チェック)されている場合、50ミリ秒は、私の場合、処理が完了するのに適切な時間だと思います。また、50msは十分に小さいので、目立ったスローダウンは発生しません。繰り返しSleep(0)になりますが、多くの場合十分であるように思われるため、あまり遅らせたくありません。

  7. コードはIO例外を再試行します。私は通常、%LocalAppData%にアクセスする例外を期待していません。そのため、単純な方法を選択し、正当な例外が発生した場合に500ミリ秒の遅延が生じるリスクを受け入れました。また、再試行したい正確な例外を検出する方法を見つけたくありませんでした。


7
PPS数か月後、この(かなり異常な)コードの一部が問題を完全に解決したことを嬉しく思います。この問題に関するサポートリクエストはゼロになっています(週あたり約1〜2)。
Andrey Tarantsov 2013年

1
+0これはより堅牢であり、「ここではありません。stackoverflow.com/a/7518831/11635よりもあなたのための完璧な解決策、私にとって同じことが当てはまります-偶然のプログラミング-注意して扱います。あなたのコードで具体一つの有用な点は、あなたが再試行をやろうとしている場合、あなたはディレクトリの最後の試み[とniave以降「ゴーン」持っているかどうかの曖昧さとレースであることを考慮しないといけないということであるDirectory.Existsガードだろうが解決しないでください。]
Ruben Bartelink 2013年

1
大好きです...私が何をしているのかわからないこれはいつも私にとっての悩みの種です...しかし、エクスプローラでディレクトリを開いているからではありません...これについてインターネットであまり騒動はありません-or-lessバグ...少なくとも私とAndreyはそれに対処する方法を持っています:)
TCC

2
@RubenBartelink OK、それで私たちはこれに同意できると思います:SOの回答は多くの初心者にとって害になるため、1つの特定のアプリで機能する(そしてすべてのケースに適することを意図したものではなかった)コードを投稿する/または無知な開発者。カスタマイズの出発点としてあげましたが、まあ、そのまま使う人もいるので、それは悪いことです。
Andrey Tarantsov 2014年

2
@nopara比較は必要ありません。ループから外れている場合は、失敗しています。そして、はい、多くの場合、例外をスローし、適切なエラー処理コードをスタックに追加します。おそらくユーザーに表示されるメッセージを使用します。
Andrey Tarantsov

18

最新の非同期回答

受け入れられた答えはまったく間違っています。ディスクからファイルを取得するのにかかる時間が、ファイルをロックしていたものを解放するため、一部の人にとってはうまくいくかもしれません。実際、これは、ファイルが他のプロセス/ストリーム/アクションによってロックされるために発生します。他の回答はThread.Sleep(Yuck)を使用して、しばらくしてからディレクトリの削除を再試行します。この質問は、より現代的な答えで再検討する必要があります。

public static async Task<bool> TryDeleteDirectory(
   string directoryPath,
   int maxRetries = 10,
   int millisecondsDelay = 30)
{
    if (directoryPath == null)
        throw new ArgumentNullException(directoryPath);
    if (maxRetries < 1)
        throw new ArgumentOutOfRangeException(nameof(maxRetries));
    if (millisecondsDelay < 1)
        throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));

    for (int i = 0; i < maxRetries; ++i)
    {
        try
        {
            if (Directory.Exists(directoryPath))
            {
                Directory.Delete(directoryPath, true);
            }

            return true;
        }
        catch (IOException)
        {
            await Task.Delay(millisecondsDelay);
        }
        catch (UnauthorizedAccessException)
        {
            await Task.Delay(millisecondsDelay);
        }
    }

    return false;
}

ユニットテスト

これらのテストは、ロックされたファイルが原因でDirectory.Deleteが失敗する原因と、TryDeleteDirectory上記の方法が問題を修正する方法の例を示しています。

[Fact]
public async Task TryDeleteDirectory_FileLocked_DirectoryNotDeletedReturnsFalse()
{
    var directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    var subDirectoryPath = Path.Combine(Path.GetTempPath(), "SubDirectory");
    var filePath = Path.Combine(directoryPath, "File.txt");

    try
    {
        Directory.CreateDirectory(directoryPath);
        Directory.CreateDirectory(subDirectoryPath);

        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
        {
            var result = await TryDeleteDirectory(directoryPath, 3, 30);
            Assert.False(result);
            Assert.True(Directory.Exists(directoryPath));
        }
    }
    finally
    {
        if (Directory.Exists(directoryPath))
        {
            Directory.Delete(directoryPath, true);
        }
    }
}

[Fact]
public async Task TryDeleteDirectory_FileLockedThenReleased_DirectoryDeletedReturnsTrue()
{
    var directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    var subDirectoryPath = Path.Combine(Path.GetTempPath(), "SubDirectory");
    var filePath = Path.Combine(directoryPath, "File.txt");

    try
    {
        Directory.CreateDirectory(directoryPath);
        Directory.CreateDirectory(subDirectoryPath);

        Task<bool> task;
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
        {
            task = TryDeleteDirectory(directoryPath, 3, 30);
            await Task.Delay(30);
            Assert.True(Directory.Exists(directoryPath));
        }

        var result = await task;
        Assert.True(result);
        Assert.False(Directory.Exists(directoryPath));
    }
    finally
    {
        if (Directory.Exists(directoryPath))
        {
            Directory.Delete(directoryPath, true);
        }
    }
}

「モダン」とはどういう意味ですか?あなたのアプローチの利点は何ですか?なぜ他の人が間違っていると思いますか?
TinyRacoon

1
他は間違っていません。彼らはThread.Sleep今日避けるべき古いAPI を使用し、代わりにasync/ awaitを使用しTask.Delayます。それは理解できる、これは非常に古い質問です。
ムハンマドレーハンサイード

このアプローチは、原因に(少なくともしない非常にリテラル線用ライン変換で)VB.Netに動作しませんBC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.
amonroejj

@amonroejj古いバージョンを使用している必要があります。それは修正されました。
ムハンマドレーハンサイード

if (!Directory.Exists(directoryPath)) { return true; } await Task.Delay(millisecondsDelay); ディレクトリが本当になくなるまでtrue を返すのではなく、少しの改善
fuchs777

16

言及すべき重要なことの1つ(コメントとして追加しましたが、許可されていません)は、オーバーロードの動作が.NET 3.5から.NET 4.0に変更されたことです。

Directory.Delete(myPath, true);

.NET 4.0以降では、フォルダー自体のファイルは削除されますが、3.5のファイルは削除されません。これは、MSDNのドキュメントでも確認できます。

.NET 4.0

指定されたディレクトリ、および指定されている場合は、ディレクトリ内のサブディレクトリとファイルを削除します

.NET 3.5

削除空のディレクトリを示している場合、および、ディレクトリ内のすべてのサブディレクトリとファイル。


3
ドキュメントの変更だけだと思います...「空のディレクトリ」だけを削除する場合、2°パラメータを使用して、ディレクトリ内のファイルも削除するのはどういう意味ですか?空の場合、ファイルはありません...
Pisu

あなたは間違っていると思います。両方のフレームワークバージョンでコードをテストした後、これを投稿しました。3.5で空でないフォルダを削除すると、例外がスローされます。
jettatore 2017

15

Delphiでも同じ問題がありました。その結果、自分のアプリケーションが、削除したいディレクトリをロックしていました。どういうわけか、書き込み中にディレクトリがロックされました(いくつかの一時ファイル)。

キャッチ22は、それを削除する前に、親のディレクトリに簡単な変更ディレクトリを作成したことです。


6
+1これで、Directory.Deleteのmsdnが言及しているものがあります!
Ruben Bartelink

3
完全なソースコードサンプルが機能する最終的な解決策はありますか?
Kiquenet 2013

11

それぞれの読み取り専用属性を変更する必要なしに、読み取り専用ファイルを含むディレクトリを削除できる、この単純な非再帰的方法について誰も考えなかったことに驚いています。

Process.Start("cmd.exe", "/c " + @"rmdir /s/q C:\Test\TestDirectoryContainingReadOnlyFiles"); 

(インターネット全体で利用可能なcmdウィンドウを一時的に起動しないように少し変更します)


私たちと共有できてうれしいですが、ネットで検索するように求めるのではなく、cmdウィンドウが呼び出されないようにするために必要な変更を少し加えてもらえますか?
ThunderGr 2012年

これは機能しません。コマンドプロンプトまたはエクスプローラからファイルを削除できる同じ状況で、このコードを使用してrmdirを呼び出すと、「ディレクトリが空ではありません」に変換される終了コード145が表示されます。Directory.Delete( ""、true)とまったく同じように、ディレクトリは空のままですが、依然として
適切

@Kevin Coulombe、Humm ... / s / qスイッチを使っていますか?
Piyush Soni

1
@KevinCoulombe:はい、それはそれらのCOMコンポーネントでなければなりません。プレーンな古いC#を試してみると、機能し、ディレクトリ内のファイル(読み取り専用または非読み取り専用のファイル)が削除されます。
Piyush Soni

5
フレームワーク内にあるべきものを外部コンポーネントに依存し始めた場合、それはもはや「理想的とは言えない」アイデアであり、もはや移植可能ではありません(またはより困難です)。exeファイルが存在しない場合はどうなりますか?または/オプションが変更されましたか?ジェレミー・エドワーズソリューションが動作する場合、それは有利な私見でなければなりません
frenchone

11

次のコマンドを実行すると、エラーを再現できます。

Directory.CreateDirectory(@"C:\Temp\a\b\c\");
Process.Start(@"C:\Temp\a\b\c\");
Thread.Sleep(1000);
Directory.Delete(@"C:\Temp\a\b\c");
Directory.Delete(@"C:\Temp\a\b");
Directory.Delete(@"C:\Temp\a");

ディレクトリ「b」を削除しようとすると、IOException「The directory is not empty」がスローされます。ディレクトリ「c」を削除したばかりなので、それはばかげています。

私の理解から、説明は、ディレクトリ「c」が削除されたものとしてスタンプされることです。ただし、削除はシステムでまだコミットされていません。システムはジョブが完了したと応答しますが、実際にはまだ処理中です。システムはおそらく、ファイルエクスプローラーが親ディレクトリにフォーカスして、削除をコミットするのを待ちます。

Delete関数のソースコード(http://referencesource.microsoft.com/#mscorlib/system/io/directory.cs)を見ると、ネイティブのWin32Native.RemoveDirectory関数を使用していることがわかります。この待機しない動作は次のとおりです。

RemoveDirectory関数は、閉じるときに削除するディレクトリをマークします。したがって、ディレクトリへの最後のハンドルが閉じられるまで、ディレクトリは削除されません。

http://msdn.microsoft.com/en-us/library/windows/desktop/aa365488(v=vs.85).aspx

スリープアンドリトライが解決策です。リアスクルのソリューションを参照してください。


8

Explorerシェルで実行できるにもかかわらず、ユーザープロファイルディレクトリ(C:\ Documents and Settings内)を削除すると、奇妙な権限の問題が発生しました。

File.SetAttributes(target_dir, FileAttributes.Normal);
Directory.Delete(target_dir, false);

「ファイル」操作がディレクトリで何をするかは私には意味がありませんが、私はそれが機能し、それで十分であることを知っています!


2
それでも望みはありません。ディレクトリに多数のファイルがあり、エクスプローラーがそれらのファイルを含むフォルダーを開いている場合です。
2013年

3

この回答はhttps://stackoverflow.com/a/1703799/184528に基づいています。私のコードとの違いは、必要な場合にのみ多くの削除サブディレクトリとファイルを再帰することです。Directory.Deleteの呼び出しは、最初の試行で失敗します(Windowsエクスプローラーがディレクトリを参照しているために発生する可能性があります)。

    public static void DeleteDirectory(string dir, bool secondAttempt = false)
    {
        // If this is a second try, we are going to manually 
        // delete the files and sub-directories. 
        if (secondAttempt)
        {
            // Interrupt the current thread to allow Explorer time to release a directory handle
            Thread.Sleep(0);

            // Delete any files in the directory 
            foreach (var f in Directory.GetFiles(dir, "*.*", SearchOption.TopDirectoryOnly))
                File.Delete(f);

            // Try manually recursing and deleting sub-directories 
            foreach (var d in Directory.GetDirectories(dir))
                DeleteDirectory(d);

            // Now we try to delete the current directory
            Directory.Delete(dir, false);
            return;
        }

        try
        {
            // First attempt: use the standard MSDN approach.
            // This will throw an exception a directory is open in explorer
            Directory.Delete(dir, true);
        }
        catch (IOException)
        {
            // Try again to delete the directory manually recursing. 
            DeleteDirectory(dir, true);
        }
        catch (UnauthorizedAccessException)
        {
            // Try again to delete the directory manually recursing. 
            DeleteDirectory(dir, true);
        } 
    }

では、フォルダがあった場合、どのようにフォルダを削除するのUnauthorizedAccessExceptionですか?再び投げるだけです。そしてまた。繰り返しますが、毎回に移動しcatchて関数を再度呼び出すためです。A Thread.Sleep(0);は権限を変更しません。エラーをログに記録し、その時点で正常に失敗するはずです。そして、このループは(サブ)ディレクトリが開いている限り継続します-プログラムで閉じません。それらが開かれたままである限り、これをそのままにしておく準備はできていますか?もっと良い方法はありますか?
vapcguy 2017年

ある場合は、UnauthorizedAccessException手動で各ファイルを削除しようとします。そのため、ディレクトリ構造をたどることによって、進歩を続けています。はい、潜在的にすべてのファイルとディレクトリが同じ例外をスローしますが、エクスプローラーがそのハンドルを保持しているために発生する可能性もあります(stackoverflow.com/a/1703799/184528を参照)「tryAgain」を「secondTry」に変更しますより明確にするために。
cdiggins 2017年

より簡潔に答えるために、「true」を渡し、別のコードパスを実行します。
cdiggins 2017年

そう、あなたの編集を見ましたが、私のポイントはファイルの削除ではなく、ディレクトリの削除です。基本的Process.Kill()に、ファイルがロックされている可能性のあるすべてのプロセスで実行できるコードをいくつか記述し、ファイルを削除しました。私が遭遇する問題は、それらのファイルの1 つがまだ開いているディレクトリを削除するときです(stackoverflow.com/questions/41841590/…を参照)。したがって、このループを逆にたどると、他に何をしていDirectory.Delete()ても、そのフォルダーで再度実行しても、そのハンドルを解放できない場合は失敗します。
vapcguy 2017年

また、UnauthorizedAccessExceptionファイルを削除しても(そのコードに到達するためにが失敗したDirectory.Delete()ために許可されていたとすると)、魔法のようにディレクトリを削除する権限が付与されないため、同じことが発生します。
vapcguy 2017年

3

上記の解決策のどれも私にはうまくいきませんでした。私は以下のように@ryasclソリューションの編集されたバージョンを使用して終了しました:

    /// <summary>
    /// Depth-first recursive delete, with handling for descendant 
    /// directories open in Windows Explorer.
    /// </summary>
    public static void DeleteDirectory(string path)
    {
        foreach (string directory in Directory.GetDirectories(path))
        {
            Thread.Sleep(1);
            DeleteDir(directory);
        }
        DeleteDir(path);
    }

    private static void DeleteDir(string dir)
    {
        try
        {
            Thread.Sleep(1);
            Directory.Delete(dir, true);
        }
        catch (IOException)
        {
            DeleteDir(dir);
        }
        catch (UnauthorizedAccessException)
        {
            DeleteDir(dir);
        }
    }

2

別のスレッドまたはプロセスがディレクトリにファイルを追加しているという競合状態がある可能性はありますか?

シーケンスは次のようになります。

削除プロセスA:

  1. ディレクトリを空にする
  2. (現在は空の)ディレクトリを削除します。

他の誰かが1と2の間にファイルを追加した場合、おそらく2はリストされた例外をスローしますか?


2

ディレクトリの削除に関するこの問題やその他の例外を解決するために数時間を費やしました。これは私の解決策です

 public static void DeleteDirectory(string target_dir)
    {
        DeleteDirectoryFiles(target_dir);
        while (Directory.Exists(target_dir))
        {
            lock (_lock)
            {
                DeleteDirectoryDirs(target_dir);
            }
        }
    }

    private static void DeleteDirectoryDirs(string target_dir)
    {
        System.Threading.Thread.Sleep(100);

        if (Directory.Exists(target_dir))
        {

            string[] dirs = Directory.GetDirectories(target_dir);

            if (dirs.Length == 0)
                Directory.Delete(target_dir, false);
            else
                foreach (string dir in dirs)
                    DeleteDirectoryDirs(dir);
        }
    }

    private static void DeleteDirectoryFiles(string target_dir)
    {
        string[] files = Directory.GetFiles(target_dir);
        string[] dirs = Directory.GetDirectories(target_dir);

        foreach (string file in files)
        {
            File.SetAttributes(file, FileAttributes.Normal);
            File.Delete(file);
        }

        foreach (string dir in dirs)
        {
            DeleteDirectoryFiles(dir);
        }
    }

このコードにはわずかな遅延がありますが、これは私のアプリケーションにとって重要ではありません。ただし、削除するディレクトリ内に多数のサブディレクトリがある場合は、遅延が問題になる可能性があることに注意してください。


8
-1遅延はどのくらいですか?偶然のプログラミングはありません!
Ruben Bartelink

1
@ルーベン私はあなたがそれについて間違っているとは言いませんでした。このためだけに反対票を投じることは厳しい罰だと私は言った。私はあなたに同意します、しかし、4つの賛成票は4つの反対票をもたらしませんでした。私もあなたのコメントに賛成票を投じますが、原因不明の遅延のため、私は回答に反対票を投じません:)
ThunderGr

1
@RubenBartelinkなど:このコードは特に好きではありませんが(同様のアプローチで別のソリューションを投稿しました)、ここでの遅延は妥当です。問題はアプリの制御外にある可能性が高いです。おそらく別のアプリがFSを定期的に再スキャンするため、フォルダが短時間ロックされます。遅延により問題が解決され、バグレポートのカウントがゼロまで減ります。根本的な原因について、ぼんやりとした考えがなくても誰が気にかけますか?
Andrey Tarantsov 2013年

1
@RubenBartelink実際、NTFSディレクトリの削除中に遅延と再試行のアプローチを使用しないことは、ここでは無責任な解決策です。どのような種類の進行中のファイルトラバーサルも削除をブロックするため、遅かれ早かれ失敗することになります。また、サードパーティの検索、バックアップ、ウイルス対策、ファイル管理のすべてのツールがフォルダから除外されるとは限りません。
Andrey Tarantsov 2013年

1
@RubenBartelink別の例では、100msの遅延を与え、ターゲットPC上のソフトウェアの最大ロック時間はAVソフトウェア= 90msです。70ミリ秒の間ファイルをロックするバックアップソフトウェアもあるとします。AVがファイルをロックすると、アプリは100ミリ秒待機しますが、通常は問題ありませんが、バックアップソフトウェアがAVスキャンの70ミリ秒のマークでファイルの取得を開始するため、別のロックが発生し、ファイルの解放にさらに40ミリ秒かかります。そのため、AVソフトウェアは時間がかかります。通常、100ミリ秒は2つのアプリのどちらよりも長くなりますが、それが途中で始まる場合についても考慮する必要があります。
vapcguy 2017年

2

ファイルを削除しない再帰的なディレクトリ削除は、予想外のことです。そのための私の修正:

public class IOUtils
{
    public static void DeleteDirectory(string directory)
    {
        Directory.GetFiles(directory, "*", SearchOption.AllDirectories).ForEach(File.Delete);
        Directory.Delete(directory, true);
    }
}

msdnに文書化されているように、これが役立つケースを経験しましたが、通常、Directory.Deleteは再帰的な削除時にディレクトリ内のファイルを削除します

Windowsエクスプローラーのユーザーとしても、この不規則な動作にときどき遭遇します。フォルダーを削除できない場合があります(無意味なメッセージは「アクセスが拒否されました」と思われます)が、ドリルダウンして下位のアイテムを削除すると、上位のアイテムを削除できますアイテムも。したがって、上記のコードは、基本クラスライブラリの問題ではなく、OSの異常を扱っていると思います。


1

ディレクトリまたはその中のファイルはロックされており、削除できません。それをロックしている犯人を見つけ、それを排除できるかどうかを確認してください。


T1000をuser-with-folder-openに:「終了しました!」
vapcguy 2017年

1

Windowsエクスプローラーでパスまたはサブフォルダーを選択するだけで、Directory.Delete(path、true)の1回の実行をブロックし、上記のようにIOExceptionをスローし、Windowsエクスプローラーを親フォルダーで起動して次のように処理する代わりに終了するようです。期待された。


これが私の問題のようです。エクスプローラーを閉じて再度実行するとすぐに、例外はありませんでした。親の親を選択するだけでも十分ではありませんでした。実際にエクスプローラを閉じなければなりませんでした。
スコットマーロウ2013

はい、これは発生し、原因です。それで、プログラムでそれを処理する方法について何か考えがありますか、それとも常に1000人のユーザー全員がそのフォルダを確実に閉じるための答えですか?
vapcguy 2017年

1

今日私はこの問題を抱えていました。これは、削除しようとしているディレクトリにWindowsエクスプローラーを開いているために発生し、再帰呼び出しが失敗し、IOExceptionが発生しました。ディレクトリに開いているハンドルがないことを確認してください。

また、MSDNは、あなた自身のrecusionを記述する必要はありませんことは明らかである。http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx


1

TFS2012を使用するビルドサーバー上のWindows Workflow Foundationでも同じ問題が発生しました。内部的には、ワークフローはDirectory.Delete()を呼び出し、再帰フラグをtrueに設定しました。私たちの場合、ネットワークに関連しているようです。

最新のバイナリを再作成して再投入する前に、ネットワーク共有上のバイナリドロップフォルダーを削除していました。他のすべてのビルドは失敗します。ビルドの失敗後にドロップフォルダを開くと、フォルダは空でした。これは、実際のディレクトリの削除を除いて、Directory.Delete()呼び出しのすべての側面が成功したことを示しています。

この問題は、ネットワークファイル通信の非同期性が原因で発生するようです。ビルドサーバーはファイルサーバーにすべてのファイルを削除するように指示し、ファイルサーバーは完全に完了していなくても、削除されたと報告しました。次に、ビルドサーバーはディレクトリの削除を要求し、ファイルサーバーはファイルの削除が完全に完了していないため、要求を拒否しました。

私たちのケースでは2つの可能な解決策:

  • 各ステップ間の遅延と検証を使用して、独自のコードで再帰的な削除を構築します
  • IOExceptionの後、最大X回再試行し、再試行する前に遅延を与える

後者の方法はすばやく簡単ですが、トリックを実行するようです。


1

これは、FileChangesNotificationsが原因です

ASP.NET 2.0以降で発生します。アプリ内のフォルダを削除すると、再起動されます。ASP.NETヘルスモニタリングを使用して、自分で確認できます 。

このコードをweb.config / configuration / system.webに追加してください:

<healthMonitoring enabled="true">
  <rules>
    <add name="MyAppLogEvents" eventName="Application Lifetime Events" provider="EventLogProvider" profile="Critical"/>
  </rules>
</healthMonitoring>


その後、チェックアウトWindows Log -> Application。何が起こっている:

フォルダを削除するとき、サブフォルダがある場合、Delete(path, true)最初にサブフォルダを削除します。FileChangesMonitorがアプリの削除とシャットダウンを知るだけで十分です。その間、メインディレクトリはまだ削除されていません。これはログからのイベントです:


ここに画像の説明を入力してください


Delete() 作業が完了せず、アプリがシャットダウンしているため、例外が発生します。

ここに画像の説明を入力してください

削除するフォルダーにサブフォルダーがない場合、Delete()はすべてのファイルとそのフォルダーを削除するだけで、アプリも再起動されますが、アプリの再起動は何も中断しないため、例外は発生しません。しかし、それでも、すべてのインプロセスセッションが失われ、再起動時にアプリがリクエストに応答しなくなります。

今何?

この動作を無効にするためのいくつかの回避策と調整があります。ディレクトリジャンクションレジストリFCNをオフにするリフレクションを使用してFileChangesMonitorを停止する(公開されたメソッドがないため)が、FCNが存在するため、すべて正しくないようです理由。これは、データの構造ではなく、アプリの構造を管理します。簡単に言えば、削除したいフォルダをアプリの外に置きます。FileChangesMonitorは通知を受信せず、アプリは毎回再起動されません。例外はありません。それらをWebから表示するには、2つの方法があります。

  1. 着信呼び出しを処理するコントローラーを作成し、アプリの外部フォルダー(wwwroot外)からフォルダーを読み取ってファイルを返します。

  2. プロジェクトが大きく、パフォーマンスが最も重要な場合は、静的コンテンツを提供するための小さくて高速なWebサーバーを個別に設定します。したがって、IISに特定の仕事を任せます。同じマシン(Windowsの場合はmongoose)または別のマシン(Linuxの場合はnginx)にある可能性があります。良いニュースは、Linuxに静的コンテンツサーバーを設定するために、追加のMicrosoftライセンスを支払う必要がないことです。

お役に立てれば。


1

上記のように、「受け入れられた」ソリューションは再解析ポイントで失敗します-それでも人々はそれをマークアップします(???)。機能を適切に複製するはるかに短いソリューションがあります:

public static void rmdir(string target, bool recursive)
{
    string tfilename = Path.GetDirectoryName(target) +
        (target.Contains(Path.DirectorySeparatorChar.ToString()) ? Path.DirectorySeparatorChar.ToString() : string.Empty) +
        Path.GetRandomFileName();
    Directory.Move(target, tfilename);
    Directory.Delete(tfilename, recursive);
}

私は知っています、後で説明するアクセス許可のケースを処理しませんが、すべての意図と目的のために、FAR BETTERは元の/ストックのDirectory.Delete()の期待される機能を提供し、コードも大幅に少なくなっています

古いdirが邪魔にならないため、安全に処理を続行でき ます。「ファイルシステムがまだ追いついている」ために(または、MSが壊れた機能を提供するために与えた言い訳)、なくなっていなくてもです

利点として、ターゲットディレクトリが大きくて深いことがわかっていて、待機したくない場合(または例外を発生させない場合)は、最後の行を次のように置き換えることができます。

    ThreadPool.QueueUserWorkItem((o) => { Directory.Delete(tfilename, recursive); });

あなたはまだ仕事を続けても安全です。


2
割り当てを簡略化できますか:string tfilename = Path.Combine(Path.GetDirectoryName(target)、Path.GetRandomFileName());
Pete

1
ピートに同意する必要があります。書かれたコードはセパレータを追加しません。それは私のパスを取り、それを\\server\C$\dir作りました\\server\C$asf.yuw。結果として、エラーが発生しましたDirectory.Move()- Source and destination path must have identical roots. Move will not work across volumes. ロックされたファイルまたは開いているディレクトリがある場合のハンドルを除いて、Peteのコードを使用すると問題なく動作したため、ThreadPoolコマンドに到達できません。
vapcguy 2017年

注意:この回答は、recursive = trueでのみ使用してください。falseの場合、空でなくてもディレクトリを移動します。これはバグでしょう。その場合の正しい動作は、例外をスローし、ディレクトリをそのままにします。
ToolmakerSteve 2018

1

この問題は、パスの長さが260シンボルを超えるディレクトリ(または任意のサブディレクトリ)にファイルがある場合に、Windowsで発生する可能性があります。

このような場合は、の\\\\?\C:\mydir代わりに削除する必要がありますC:\mydir。約260のシンボル制限は、ここで読むことができます


1

私はこの千年紀のテクニックで解決しました(キャッチでThread.Sleepを自分で残すことができます)

bool deleted = false;
        do
        {
            try
            {
                Directory.Delete(rutaFinal, true);                    
                deleted = true;
            }
            catch (Exception e)
            {
                string mensaje = e.Message;
                if( mensaje == "The directory is not empty.")
                Thread.Sleep(50);
            }
        } while (deleted == false);

0

アプリケーション(または他のアプリケーション)の現在のディレクトリが削除しようとしているディレクトリである場合、アクセス違反エラーにはなりませんが、ディレクトリは空ではありません。現在のディレクトリを変更して、自分のアプリケーションではないことを確認してください。また、ディレクトリが他のプログラム(Word、Excel、Total Commanderなど)で開いていないことを確認してください。ほとんどのプログラムは、最後に開かれたファイルのディレクトリにcdします。


0

ネットワークファイルの場合、Directory.DeleteHelper(recursive:= true)は、ファイルの削除の遅延が原因でIOExceptionを引き起こす可能性があります


0

私はあなたが気付いていないいくつかのストリームによって開かれているファイルがあると思います。同じ問題があり、削除したいディレクトリを指しているすべてのストリームを閉じることで解決しました。


0

このエラーは、ファイルまたはディレクトリが使用中と見なされた場合に発生します。これは誤解を招くエラーです。ツリー内の任意のディレクトリ、またはそのツリー内のファイルを使用しているプログラムに対して開かれているエクスプローラーウィンドウまたはコマンドラインウィンドウがあるかどうかを確認します。


0

メソッドが非同期であり、次のようにコーディングされている場合、上記の問題の考えられる1つのインスタンスを解決しました。

// delete any existing update content folder for this update
if (await fileHelper.DirectoryExistsAsync(currentUpdateFolderPath))
       await fileHelper.DeleteDirectoryAsync(currentUpdateFolderPath);

これとともに:

bool exists = false;                
if (await fileHelper.DirectoryExistsAsync(currentUpdateFolderPath))
    exists = true;

// delete any existing update content folder for this update
if (exists)
    await fileHelper.DeleteDirectoryAsync(currentUpdateFolderPath);

結論?Microsoftが話すことができなかった存在をチェックするために使用されるハンドルを取り除くことの非同期の側面があります。これは、ifステートメント内の非同期メソッドに、usingステートメントのように機能するifステートメントがあるかのようです。


0

再帰性のための追加のメソッドを作成したり、フォルダーextra内のファイルを削除したりする必要はありません。これはすべて呼び出すことで自動的に行われます

DirectoryInfo.Delete();

詳細はこちら

このようなものはかなりうまくいきます:

  var directoryInfo = new DirectoryInfo("My directory path");
    // Delete all files from app data directory.

    foreach (var subDirectory in directoryInfo.GetDirectories())
    {
          subDirectory.Delete(true);// true set recursive paramter, when it is true delete sub file and sub folder with files too
    }

削除メソッドに変数としてtrueを渡すと、サブファイルとファイルを含むサブフォルダーも削除されます。


-2

上記の答えはどれもうまくいきませんでした。DirectoryInfoターゲットディレクトリでの自分のアプリの使用が原因で、アプリがロックされたままになっているようです。

ガベージコレクションを強制すると問題が解決するように見えましたが、すぐには解決しませんでした。必要に応じて数回削除を試みます。

Directory.Exists例外の後で消えることがあるので注意してください。削除が遅れた理由がわかりません(Windows 7 SP1)

        for (int attempts = 0; attempts < 10; attempts++)
        {
            try
            {
                if (Directory.Exists(folder))
                {
                    Directory.Delete(folder, true);
                }
                return;
            }
            catch (IOException e)
            {
                GC.Collect();
                Thread.Sleep(1000);
            }
        }

        throw new Exception("Failed to remove folder.");

1
-1偶然のプログラミング。GCしたとき、どのオブジェクトが何をしますか?これは何らかの方法で一般的なアドバイスですか?(問題があり、このコードを使用していて、今は問題がないと感じていると思いますが、それは問題ではありません)
Ruben Bartelink

@RubenBartelink同意する。それはハックです。それが何をどのように解決しているかが明確でないときに何かを行うブードゥー教のコード。私は適切な解決策が大好きです。
Reactgular 2014年

1
私の問題は、stackoverflow.com / a / 14933880/11635に加えて何かが非常に投機的であることです。できれば、重複の場合は-1を、偶然の推測/プログラミングの場合は-1を指定します。散水GC.Collectは、a)悪いアドバイスであり、b)ロックされたdirsの一般的な原因として十分に一般的ではないため、ここに含める必要があります。ただ、他人のものを選択し、罪のない読者の心の中にまくより多くのない混乱を行う
ルーベンBartelink

3
GC.WaitForPendingFinalizers();を使用します。GC.Collect();の後 これは期待どおりに動作します。
Heiner 2014

わからない、テストされていないが、おそらくより良いと何かをするだろうusing、その後、声明: using (DirectoryInfo di = new DirectoryInfo(@"c:\MyDir")) { for (int attempts = 0; attempts < 10; attempts++) { try { if (di.Exists(folder)) { Directory.Delete(folder, true); } return; } catch (IOException e) { Thread.Sleep(1000); } } }
vapcguy

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.