ไม่สามารถลบไดเรกทอรีด้วย Directory.Delete (เส้นทาง, จริง)


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)
    ...

ฉันไม่แปลกใจที่บางครั้งวิธีการโยน แต่ฉันประหลาดใจที่ได้รับข้อความนี้เมื่อเรียกซ้ำเป็นจริง (ฉันรู้ว่าไดเรกทอรีไม่ว่างเปล่า)

มีเหตุผลที่ฉันเห็นสิ่งนี้แทน AccessViolationException หรือไม่


13
คุณจะไม่เห็น AccessViolationException - เป็นการดำเนินการของตัวชี้ที่ไม่ถูกต้องไม่ใช่เพื่อการเข้าถึงดิสก์
Joe White

1
นี่ดูเหมือนจะเป็นปัญหาของ IO บางอย่างนอกเหนือจากไดเรกทอรีที่ไม่ว่างเปล่าเช่นตัวจัดการไฟล์เปิดหรืออะไรบางอย่าง ฉันจะลองใช้ตัวเลือกการลบแบบเรียกซ้ำจากนั้นค้นหา IOException ค้นหาและปิดตัวจัดการไฟล์ที่เปิดอยู่แล้วลองอีกครั้ง มีการอภิปรายเกี่ยวกับเรื่องนี้ที่นี่: stackoverflow.com/questions/177146/…
Dan Csharpster

คำตอบ:


231

หมายเหตุบรรณาธิการ:Directory.Deleteถึงแม้ว่าคำตอบนี้มีข้อมูลที่เป็นประโยชน์บางอย่างก็เป็นความจริงที่ไม่ถูกต้องเกี่ยวกับการทำงานของ โปรดอ่านความคิดเห็นสำหรับคำตอบนี้และคำตอบอื่น ๆ สำหรับคำถามนี้


ฉันพบปัญหานี้มาก่อน

รากของปัญหาคือฟังก์ชั่นนี้ไม่ได้ลบไฟล์ที่อยู่ในโครงสร้างไดเรกทอรี ดังนั้นสิ่งที่คุณต้องทำคือสร้างฟังก์ชั่นที่จะลบไฟล์ทั้งหมดในโครงสร้างไดเรกทอรีจากนั้นไดเรกทอรีทั้งหมดก่อนที่จะลบไดเรกทอรีเอง ฉันรู้ว่าสิ่งนี้ขัดกับพารามิเตอร์ตัวที่สอง แต่มันเป็นวิธีที่ปลอดภัยกว่ามาก นอกจากนี้คุณอาจต้องการลบคุณลักษณะการเข้าถึงแบบอ่านอย่างเดียวจากไฟล์ก่อนที่คุณจะลบออก มิฉะนั้นจะยกข้อยกเว้น

เพียงตบรหัสนี้ในโครงการของคุณ

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) เป็นโอเวอร์โหลดที่ลบไฟล์ทั้งหมดที่อยู่ในโครงสร้างไดเรกทอรี หากคุณต้องการที่จะผิดให้ทำผิดกับคำตอบของ Ryan S
ซิก Tolleranza

35
+1 เนื่องจากแม้ว่า Directory.Delete () จะลบไฟล์ภายในไดเรกทอรีย่อย (ด้วย recursive = true) แต่จะส่ง "IOException: Directory ไม่ว่างเปล่า" ถ้าหนึ่งในไดเรกทอรีย่อยหรือไฟล์เป็นแบบอ่านอย่างเดียว ดังนั้นวิธีนี้ใช้งานได้ดีกว่า 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)ล้มเหลวสิ่งที่ถูกล็อคหรือผิดพลาดและไม่มีขนาดที่เหมาะกับการแก้ไขปัญหาดังกล่าวทั้งหมด ผู้คนจำเป็นต้องจัดการกับปัญหานั้นในบริบทของพวกเขาและเรา shouddnt จะเติบโตขนปุยขนาดใหญ่ทุกความคิดที่มีปัญหา (กับ retries และข้อยกเว้นการกลืน) และหวังว่าจะได้ผลลัพธ์ที่ดี
Ruben Bartelink

37
ระวังวิธีการนี้หากไดเรกทอรีของคุณลบมีทางลัด / ลิงก์สัญลักษณ์ไปยังโฟลเดอร์อื่น ๆ - คุณอาจต้องลบมากกว่าที่คุณคาดไว้
Chanakya

182

หากคุณพยายามที่จะลบไดเรกทอรีซ้ำaและa\bเปิดไดเรกทอรีใน Explorer bจะถูกลบ แต่คุณจะได้รับข้อผิดพลาด 'ไดเรกทอรีไม่ว่าง' aแม้ว่ามันจะว่างเปล่าเมื่อคุณไปดู ไดเรกทอรีปัจจุบันของโปรแกรมใด ๆ (รวมทั้ง Explorer) ยังคงมีการจัดการไปยังไดเรกทอรี เมื่อคุณเรียกDirectory.Delete(true)มันลบจากด้านล่างขึ้น: แล้วb aหากbเปิดอยู่ใน Explorer Explorer จะตรวจจับการลบbเปลี่ยนไดเรกทอรีขึ้นcd ..และล้างจุดจับเปิด เนื่องจากระบบไฟล์ทำงานแบบอะซิงโครนัสการDirectory.Deleteดำเนินการล้มเหลวเนื่องจากข้อขัดแย้งกับ Explorer

โซลูชันที่ไม่สมบูรณ์

ฉันโพสต์วิธีแก้ไขปัญหาต่อไปนี้โดยเริ่มต้นด้วยแนวคิดของการขัดจังหวะเธรดปัจจุบันเพื่อให้เวลา Explorer ปล่อยตัวจัดการไดเรกทอรี

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

แต่นี้จะทำงานเฉพาะถ้าไดเรกทอรีเปิดเป็นทันทีลูกของไดเรกทอรีที่คุณกำลังลบ หากa\b\c\dมีการเปิดใน Explorer และคุณใช้เกี่ยวกับเรื่องนี้aเทคนิคนี้จะล้มเหลวหลังจากลบและdc

ทางออกที่ค่อนข้างดีกว่า

วิธีนี้จะจัดการกับการลบโครงสร้างไดเรกทอรีที่ลึกแม้ว่าหนึ่งในไดเรกทอรีระดับล่างเปิดใน Explorer

/// <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ที่สามารถเกิดขึ้นได้ตลอดทาง ไม่ชัดเจนว่าความพยายามในการลบครั้งแรกนั้นเป็นการปูทางไปสู่ความสำเร็จครั้งที่สองหรือไม่หรือถ้าเป็นเพียงการหน่วงเวลาตามคำแนะนำจากการขว้างปา / จับข้อยกเว้นที่อนุญาตให้ระบบไฟล์ติดตาม

คุณอาจสามารถลดจำนวนข้อยกเว้นที่ถูกโยนและถูกจับได้ภายใต้เงื่อนไขทั่วไปโดยการเพิ่ม a Thread.Sleep(0)ที่จุดเริ่มต้นของtryบล็อก นอกจากนี้ยังมีความเสี่ยงที่ภายใต้ภาระของระบบจำนวนมากคุณสามารถบินผ่านทั้งDirectory.Deleteความพยายามและความล้มเหลว พิจารณาโซลูชันนี้เป็นจุดเริ่มต้นสำหรับการลบแบบเรียกซ้ำที่มีประสิทธิภาพมากขึ้น

คำตอบทั่วไป

วิธีนี้แก้ไขปัญหาเฉพาะของการโต้ตอบกับ Windows Explorer หากคุณต้องการการลบแบบแข็ง ๆ สิ่งหนึ่งที่ต้องจำไว้ก็คือทุกสิ่ง (ตัวสแกนไวรัสหรืออะไรก็ตาม) อาจมีจุดเปิดแบบเปิดสำหรับสิ่งที่คุณพยายามลบได้ตลอดเวลา ดังนั้นคุณต้องลองอีกครั้งในภายหลัง จำนวนเท่าใดในภายหลังและจำนวนครั้งที่คุณลองขึ้นอยู่กับความสำคัญของวัตถุที่ถูกลบ ในฐานะที่เป็นMSDN บ่งชี้ ,

รหัสการวนซ้ำไฟล์ที่แข็งแกร่งต้องคำนึงถึงความซับซ้อนหลายประการของระบบไฟล์

ข้อความบริสุทธิ์นี้มาพร้อมกับลิงก์ไปยังเอกสารอ้างอิง NTFS เท่านั้นควรทำให้เส้นขนของคุณดูโดดเด่นขึ้น

( แก้ไข : เยอะมากคำตอบนี้มี แต่โซลูชันแรกที่ไม่สมบูรณ์เท่านั้น)


11
มันจะปรากฏขึ้นเรียก Directory.Delete (เส้นทางจริง) ในขณะที่เส้นทางหรือหนึ่งในโฟลเดอร์ / ไฟล์ภายใต้เส้นทางเปิดหรือเลือกใน Windows Explorer จะโยน IOException การปิด Windows Explorer และรับรหัสที่มีอยู่ของฉันอีกครั้งโดยไม่มีการลอง / จับที่แนะนำข้างต้นทำงานได้ดี
David Alpert

1
ฉันไม่สามารถเข้าใจได้ว่ามันจะทำงานอย่างไรและทำไม แต่มันใช้งานได้สำหรับฉันในขณะที่ตั้งค่าคุณสมบัติไฟล์และการเขียนฟังก์ชั่นวนซ้ำของตัวเองไม่ได้
Stilgar

1
@CarlosLiu เพราะมันคือการให้ "Explorer ที่มีโอกาสที่จะปล่อยจับไดเรกทอรี"
Dmitry Gonchar

4
สิ่งที่เกิดขึ้นคือระบบขอให้ Explorer "ปล่อยตัวจัดการไดเรกทอรี" จากนั้นพยายามลบไดเรกทอรี หากหมายเลขอ้างอิงไดเรกทอรีไม่ถูกลบในเวลาข้อยกเว้นจะถูกยกขึ้นและcatchบล็อกจะถูกดำเนินการ (ในขณะเดียวกัน Explorer ก็ยังคงปล่อยไดเรกทอรีดังกล่าวเนื่องจากไม่มีคำสั่งใดถูกส่งไปเพื่อบอกให้ทำเช่นนั้น) การเรียกไปยังThread.Sleep(0)อาจหรือไม่จำเป็นเนื่องจากcatchบล็อกได้ให้เวลากับระบบอีกเล็กน้อย แต่ก็ให้ความปลอดภัยเพิ่มขึ้นเล็กน้อยในราคาที่ต่ำ หลังจากนั้นชื่อDeleteถูกเรียกใช้พร้อมกับไดเร็กทอรีที่เผยแพร่แล้ว
Zachary Kniebel

1
@PandaWood จริง ๆ แล้วมีเพียงการนอนหลับนี้ (100) ที่เหมาะกับฉัน การนอนหลับ (0) ไม่ทำงาน ฉันไม่รู้ว่าเกิดอะไรขึ้นและจะแก้ปัญหานี้อย่างไร ฉันหมายความว่าถ้ามันขึ้นอยู่กับโหลดเซิร์ฟเวอร์และในอนาคตควรมี 300 หรือ 400 จะรู้ได้อย่างไรว่า ต้องเป็นอีกวิธีการที่เหมาะสม ...
โรมัน

43

ก่อนดำเนินการต่อให้ตรวจสอบสาเหตุต่อไปนี้ที่อยู่ภายใต้การควบคุมของคุณ:

  • โฟลเดอร์ถูกตั้งค่าเป็นไดเรกทอรีปัจจุบันของกระบวนการของคุณหรือไม่? ถ้าใช่เปลี่ยนเป็นอย่างอื่นก่อน
  • คุณเปิดไฟล์ (หรือโหลด DLL) จากโฟลเดอร์นั้นหรือไม่? (และลืมปิด / ยกเลิกการโหลด)

มิฉะนั้นให้ตรวจสอบเหตุผลที่ถูกต้องต่อไปนี้นอกการควบคุมของคุณ:

  • มีไฟล์ที่ทำเครื่องหมายว่าอ่านอย่างเดียวในโฟลเดอร์นั้น
  • คุณไม่ได้รับอนุญาตให้ลบไฟล์เหล่านั้นบางไฟล์
  • ไฟล์หรือโฟลเดอร์ย่อยเปิดใน Explorer หรือแอปอื่น

หากข้อใดข้อหนึ่งข้างต้นเป็นปัญหาคุณควรเข้าใจว่าเหตุใดจึงเกิดขึ้นก่อนที่จะพยายามปรับปรุงรหัสการลบของคุณ แอปของคุณควรลบไฟล์แบบอ่านอย่างเดียวหรือไม่สามารถเข้าถึงได้ ใครทำเครื่องหมายไว้อย่างนั้นและทำไม?

เมื่อคุณยกเลิกเหตุผลข้างต้นแล้วยังมีความเป็นไปได้ที่จะเกิดความล้มเหลวปลอม การลบจะล้มเหลวหากใครถือที่จับกับไฟล์หรือโฟลเดอร์ใด ๆ ที่ถูกลบและมีสาเหตุหลายประการว่าทำไมบางคนอาจระบุโฟลเดอร์หรืออ่านไฟล์:

  • ค้นหาดัชนี
  • ป้องกันไวรัส
  • ซอฟต์แวร์สำรองข้อมูล

วิธีการทั่วไปในการจัดการกับความล้มเหลวแบบลวงตาคือลองหลาย ๆ ครั้งระหว่างหยุดชั่วคราว เห็นได้ชัดว่าคุณไม่ต้องการที่จะพยายามตลอดไปดังนั้นคุณควรยอมแพ้หลังจากพยายามหลายครั้งและโยนข้อยกเว้นหรือเพิกเฉยต่อข้อผิดพลาด แบบนี้:

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) นี่เป็นขีด จำกัด โดยพลการซึ่งใช้งานได้จริง ฉันต้องการให้ขีด จำกัด สั้นพอที่ผู้ใช้จะไม่ฆ่าแอปโดยคิดว่ามันหยุดตอบสนอง ในทางตรงกันข้ามครึ่งวินาทีนั้นมีเวลาอีกมากที่ผู้กระทำผิดจะประมวลผลโฟลเดอร์ของฉันให้เสร็จ ตัดสินจากคำตอบ SO อื่น ๆ ซึ่งบางครั้งพบว่าSleep(0)เป็นที่ยอมรับได้ แต่ผู้ใช้เพียงไม่กี่รายจะได้รับประสบการณ์มากกว่าการลองใหม่เพียงครั้งเดียว

  6. ฉันลองใหม่ทุก ๆ 50ms ซึ่งเป็นอีกจำนวนหนึ่งโดยพลการ ฉันรู้สึกว่าหากไฟล์กำลังถูกประมวลผล (ทำดัชนีตรวจสอบแล้ว) เมื่อฉันพยายามลบมัน 50ms เป็นเวลาที่เหมาะสมในการคาดหวังว่าการประมวลผลจะเสร็จสมบูรณ์ในกรณีของฉัน นอกจากนี้ 50ms มีขนาดเล็กพอที่จะไม่ทำให้เกิดการชะลอตัวที่เห็นได้ชัด อีกครั้งSleep(0)ดูเหมือนจะเพียงพอในหลายกรณีดังนั้นเราไม่ต้องการล่าช้ามากเกินไป

  7. รหัสจะลองใช้กับข้อยกเว้น IO ใด ๆ ปกติฉันไม่คาดหวังว่าจะมีข้อยกเว้นใด ๆ ในการเข้าถึง% LocalAppData% ดังนั้นฉันเลือกความเรียบง่ายและยอมรับความเสี่ยงของการล่าช้า 500ms ในกรณีที่มีข้อยกเว้นทางกฎหมายเกิดขึ้น ฉันไม่ต้องการที่จะหาวิธีในการตรวจสอบข้อยกเว้นที่แน่นอนที่ฉันต้องการลองใหม่อีกครั้ง


7
PPS ไม่กี่เดือนต่อมาฉันดีใจที่ได้รายงานว่าชิ้นส่วนของโค้ด (ค่อนข้างบ้า) นี้ได้แก้ไขปัญหานี้แล้ว คำขอการสนับสนุนเกี่ยวกับปัญหานี้ลดลงเหลือศูนย์ (จากประมาณ 1-2 ต่อสัปดาห์)
Andrey Tarantsov

1
+0 ในขณะที่นี่แข็งแกร่งขึ้นและน้อยลง 'ที่นี่คือ; โซลูชั่นที่สมบูรณ์แบบสำหรับคุณ 'กว่าstackoverflow.com/a/7518831/11635สำหรับฉันเช่นเดียวกัน - การเขียนโปรแกรมโดยบังเอิญ - จัดการด้วยความระมัดระวัง จุดหนึ่งที่มีประโยชน์ที่เป็นตัวเป็นตนในรหัสของคุณคือถ้าคุณจะลองอีกครั้งคุณต้องพิจารณาว่าคุณกำลังแข่งกับความกำกวมว่าสารบบนั้นมี 'หายไป' ตั้งแต่พยายามครั้งสุดท้าย [และDirectory.Existsเจ้าหน้าที่รักษาความปลอดภัยของniave จะ ไม่สามารถแก้ไขได้]
Ruben Bartelink

1
รักมัน ... ไม่รู้ว่าฉันกำลังทำอะไรอยู่นี่เป็นจุดเจ็บปวดสำหรับฉันเสมอ ... แต่ไม่ใช่เพราะฉันมีไดเรกทอรีเปิดอยู่ใน explorer ... ไม่มากความวุ่นวายบนอินเทอร์เน็ตเกี่ยวกับเรื่องนี้อีก -or น้อยข้อผิดพลาด ... อย่างน้อยฉันและอันเดรย์มีวิธีที่จะจัดการกับมัน :)
ทีซีซี

2
@RubenBartelink ตกลงดังนั้นฉันคิดว่าเราสามารถเห็นด้วยกับเรื่องนี้: การโพสต์โค้ดที่เหมาะกับแอปหนึ่ง (และไม่เคยมีความเหมาะสมสำหรับทุกกรณี) เนื่องจากคำตอบ SO เป็นสิ่งที่สร้างความเสียหายให้กับมือใหม่จำนวนมาก / หรือนักพัฒนาที่ไม่รู้ ฉันให้มันเป็นจุดเริ่มต้นสำหรับการปรับแต่ง แต่ใช่บางคนจะใช้มันตามที่เป็นอยู่และนั่นเป็นสิ่งที่ไม่ดี
Andrey Tarantsov

2
@nopara คุณไม่จำเป็นต้องทำการเปรียบเทียบ ถ้าเราออกจากวงเราล้มเหลว และใช่ในหลายกรณีคุณจะต้องทิ้งข้อผิดพลาดจากนั้นเพิ่มข้อผิดพลาดที่เหมาะสมในการจัดการรหัสขึ้นสแต็คน่าจะมีข้อความที่ผู้ใช้มองเห็น
Andrey Tarantsov

18

คำตอบ Async ที่ทันสมัย

คำตอบที่ยอมรับนั้นผิดธรรมดามันอาจใช้ได้กับบางคนเพราะเวลาที่ใช้ในการรับไฟล์จากดิสก์ทำให้ทุกอย่างถูกล็อกไฟล์ ความจริงก็คือสิ่งนี้เกิดขึ้นเพราะไฟล์ถูกล็อคโดยกระบวนการ / สตรีม / แอ็คชันอื่น ๆ คำตอบอื่น ๆ ใช้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
คนอื่นไม่ผิด พวกเขาใช้ API แบบเก่าThread.Sleepที่คุณควรหลีกเลี่ยงในวันนี้และใช้async/ awaitด้วยTask.Delayแทน เป็นเรื่องที่เข้าใจได้นี่เป็นคำถามที่เก่ามาก
Muhammad Rehan Saeed

วิธีนี้จะไม่ทำงานใน VB.Net (อย่างน้อยก็ไม่ใช่การแปลงบรรทัดต่อบรรทัด) เนื่องจากBC36943 'Await' cannot be used inside a 'Catch' statement, a 'Finally' statement, or a 'SyncLock' statement.
amonroejj

@amonroejj คุณต้องใช้เวอร์ชั่นที่เก่ากว่า นั่นคือการแก้ไข
Muhammad Rehan Saeed

การปรับปรุงเล็ก ๆ น้อย ๆ แทนที่จะกลับมาจริง if (!Directory.Exists(directoryPath)) { return true; } await Task.Delay(millisecondsDelay); เพื่อรอจนกว่าไดเรกทอรีหายไปจริงๆ
fuchs777

16

สิ่งสำคัญสิ่งหนึ่งที่ควรกล่าวถึง (ฉันเพิ่มเป็นความคิดเห็น แต่ฉันไม่ได้รับอนุญาต) คือพฤติกรรมของโอเวอร์โหลดเปลี่ยนจาก. 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

15

ฉันมีปัญหาเดียวกันภายใต้ Delphi และผลลัพธ์สุดท้ายก็คือแอปพลิเคชันของฉันถูกล็อคไดเรกทอรีที่ฉันต้องการลบ อย่างใดไดเรกทอรีถูกล็อคเมื่อฉันเขียนมัน (บางไฟล์ชั่วคราว)

อันดับที่ 22 คือฉันสร้างไดเรกทอรีการเปลี่ยนแปลงอย่างง่ายให้กับผู้ปกครองก่อนที่จะลบมัน


6
+1 ตอนนี้มีบางอย่างที่msdn สำหรับ Directory.Deleteพูดถึง!
Ruben Bartelink

3
โซลูชันขั้นสุดท้ายใดที่มีตัวอย่างซอร์สโค้ดเต็มรูปแบบที่ทำงานเกี่ยวกับมัน
Kiquenet

11

ฉันประหลาดใจที่ไม่มีใครคิดถึงวิธีการที่ไม่ต้องเรียกซ้ำง่าย ๆ นี้ซึ่งสามารถลบไดเรกทอรีที่มีไฟล์อ่านอย่างเดียวโดยไม่จำเป็นต้องเปลี่ยนแอตทริบิวต์อ่านอย่างเดียวของแต่ละไฟล์

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

(เปลี่ยนนิดหน่อยเพื่อไม่ให้ไฟหน้าต่าง cmd เกิดขึ้นชั่วคราวซึ่งสามารถใช้ได้ทั่วอินเทอร์เน็ต)


ดีใจที่ได้แบ่งปันกับเรา แต่คุณอยากจะรวมส่วนของการเปลี่ยนแปลงที่จำเป็นเพื่อป้องกันการยิงหน้าต่าง cmd แทนที่จะให้เราค้นหาผ่านอินเทอร์เน็ตหรือไม่
ThunderGr

มันใช้งานไม่ได้ ในสถานการณ์เดียวกันที่ฉันสามารถลบไฟล์จาก command prompt หรือ Explorer การใช้รหัสนี้เพื่อโทร rmdir ให้รหัสออก 145 ซึ่งแปลว่า "ไดเรกทอรีไม่ว่าง" มันปล่อยให้ไดเรกทอรีว่างเปล่า แต่ยังคงอยู่ในสถานที่เช่นกันเช่นเดียวกับ Directory.Delete ("", จริง)
Kevin Coulombe

@Kevin Coulombe, Humm ... คุณแน่ใจหรือว่าใช้สวิตช์ / s / q
Piyush Soni

1
@KevinCoulombe: ใช่มันต้องเป็นส่วนประกอบ COM เหล่านั้น เมื่อฉันลองใช้ C # แบบธรรมดาธรรมดามันใช้งานได้และมันจะลบไดเรกทอรีพร้อมกับไฟล์ข้างใน (อ่านอย่างเดียวหรือไม่อ่านอย่างเดียว)
Piyush Soni

5
หากคุณเริ่มพึ่งพาส่วนประกอบภายนอกสำหรับสิ่งที่ควรอยู่ในกรอบจากนั้นเป็นแนวคิด "น้อยกว่าอุดมคติ" เพราะมันไม่ใช่แบบพกพาอีกต่อไป (หรือยากกว่า) เกิดอะไรขึ้นถ้า exe ไม่อยู่ที่นั่น? หรือตัวเลือก / การเปลี่ยนแปลง? หากวิธีการแก้ปัญหาโดย Jeremy Edwards นั้นควรใช้ IMHO
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 "ไดเรกทอรีไม่ว่าง" นั่นโง่เพราะเราเพิ่งลบไดเรกทอรี 'c'

จากความเข้าใจของฉันคำอธิบายคือไดเรกทอรี 'c' ถูกประทับตราว่าถูกลบ แต่การลบยังไม่ได้กระทำในระบบ ระบบตอบกลับงานเสร็จแล้วในขณะที่ในความเป็นจริงมันยังคงดำเนินการอยู่ ระบบอาจรอให้ file explorer มีโฟกัสที่ไดเร็กทอรีพาเรนต์เพื่อคอมมิตการลบ

ถ้าคุณดูรหัสแหล่งที่มาของฟังก์ชันลบ ( 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 )

นอนและลองใหม่เป็นวิธีแก้ปัญหา เทียบกับโซลูชันของ ryascl


8

ฉันมีปัญหาเกี่ยวกับการอนุญาตแปลก ๆ เหล่านี้ในการลบไดเรกทอรีโปรไฟล์ผู้ใช้ (ใน C: \ Documents and Settings) แม้ว่าจะสามารถทำได้ในเชลล์ Explorer

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

มันไม่มีเหตุผลสำหรับฉันในการดำเนินการ "ไฟล์" ในไดเรกทอรี แต่ฉันรู้ว่ามันใช้งานได้และนั่นก็เพียงพอแล้วสำหรับฉัน!


2
ยังคงไม่มีความหวังเมื่อไดเรกทอรีมีไฟล์จำนวนมากและ Explorer กำลังเปิดโฟลเดอร์ที่มีไฟล์เหล่านั้น
เห็น

3

คำตอบนี้จะขึ้นอยู่กับตาม: https://stackoverflow.com/a/1703799/184528 ความแตกต่างกับรหัสของฉันคือเราเพียงเรียกคืนการลบไดเรกทอรีย่อยและไฟล์จำนวนมากเมื่อจำเป็นต้องเรียก Directory.Delete ล้มเหลวในความพยายามครั้งแรก (ซึ่งอาจเกิดขึ้นเนื่องจาก windows explorer ดูที่ไดเรกทอรี)

    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และเรียกใช้ฟังก์ชันอีกครั้ง Thread.Sleep(0);ไม่เปลี่ยนแปลงสิทธิ์ของคุณ มันควรบันทึกข้อผิดพลาดและล้มเหลวอย่างสง่างาม ณ จุดนั้น และลูปนี้จะดำเนินต่อไปตราบใดที่ไดเรกทอรี (ย่อย) เปิดอยู่ - มันไม่ปิดโดยทางโปรแกรม เราพร้อมที่จะปล่อยให้ทำสิ่งนี้ตราบเท่าที่สิ่งเหล่านั้นถูกเปิดทิ้งไว้หรือไม่? มีวิธีที่ดีกว่า?
vapcguy

หากมีUnauthorizedAccessExceptionมันจะพยายามลบแต่ละไฟล์ด้วยตนเอง ดังนั้นจึงยังคงมีความคืบหน้าโดยเข้าไปในโครงสร้างไดเรกทอรี ใช่อาจเป็นไปได้ว่าทุกไฟล์และไดเรกทอรีจะมีข้อยกเว้นเหมือนกัน แต่สิ่งนี้อาจเกิดขึ้นเพียงเพราะ explorer ถือที่จับไว้ (ดูstackoverflow.com/a/1703799/184528 ) ฉันจะเปลี่ยน "tryAgain" เป็น "secondTry" เพื่อให้ชัดเจนยิ่งขึ้น
cdiggins

ในการตอบคำถามให้สำเร็จลุล่วงมันจะผ่าน "จริง" และดำเนินการกับเส้นทางรหัสอื่น
cdiggins

ถูกต้องเห็นการแก้ไขของคุณ แต่ประเด็นของฉันไม่ได้อยู่ที่การลบไฟล์ แต่เป็นการลบไดเรกทอรี ฉันเขียนรหัสบางอย่างที่ฉันสามารถทำได้Process.Kill()ในกระบวนการใด ๆ ที่ไฟล์อาจถูกล็อคและลบไฟล์ ปัญหาที่ฉันพบคือเมื่อลบไดเรกทอรีที่หนึ่งในไฟล์เหล่านั้นยังคงเปิดอยู่ (ดูstackoverflow.com/questions/41841590/… ) ดังนั้นการย้อนกลับผ่านลูปนี้ไม่ว่าจะทำอะไรถ้ามันทำDirectory.Delete()ในโฟลเดอร์นั้นอีกครั้งมันจะยังคงล้มเหลวหากหมายเลขอ้างอิงนั้นไม่สามารถเผยแพร่ได้
vapcguy

และเช่นเดียวกันจะเกิดขึ้นสำหรับUnauthorizedAccessExceptionตั้งแต่การลบไฟล์ (สมมติว่านี่เป็นสิ่งที่ได้รับอนุญาตแม้จะได้รับรหัสนั้นมันล้มเหลวในDirectory.Delete()) ไม่ได้ให้สิทธิ์ในการลบไดเรกทอรีอย่างน่าอัศจรรย์
vapcguy

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

เป็นไปได้หรือไม่ที่คุณมีสภาวะการแย่งชิงที่เธรดหรือกระบวนการอื่นกำลังเพิ่มไฟล์ในไดเรกทอรี

ลำดับจะเป็น:

กระบวนการ Deleter 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
@ Ruben ฉันไม่ได้บอกว่าคุณผิดเกี่ยวกับเรื่องนี้ ฉันแค่บอกว่าการ downvoting เพียงเพราะนี่เป็นการลงโทษที่รุนแรง ฉันเห็นด้วยกับคุณแล้วอย่างไรก็ตาม upvotes 4 รายการไม่ได้ส่งผลให้มี 4 downvote ฉันจะ upvote ความคิดเห็นของคุณได้เป็นอย่างดี แต่ฉันจะไม่ downvote คำตอบเนื่องจากมีความล่าช้าไม่ได้อธิบาย :)
ThunderGr

1
@RubenBartelink และคนอื่น ๆ : ในขณะที่ฉันไม่ชอบรหัสนี้โดยเฉพาะ (ฉันได้โพสต์โซลูชันอื่นด้วยวิธีการที่คล้ายกัน) ความล่าช้าที่นี่เหมาะสม ปัญหาน่าจะอยู่นอกเหนือการควบคุมของแอพ แอพอื่นอาจยกเลิก FS เป็นระยะดังนั้นจึงล็อคโฟลเดอร์เป็นระยะเวลาสั้น ๆ ความล่าช้าในการแก้ไขปัญหาการรายงานข้อผิดพลาดนับเป็นศูนย์ ใครจะสนใจถ้าเราไม่มีความคิดที่เป็นสาเหตุให้เกิดใคร?
Andrey Tarantsov

1
@RubenBartelink ในความเป็นจริงเมื่อคุณคิดเกี่ยวกับมันไม่ได้ใช้วิธีการล่าช้าและลองระหว่างการลบไดเรกทอรี NTFS เป็นทางออกที่ขาดความรับผิดชอบที่นี่ การข้ามผ่านไฟล์อย่างต่อเนื่องใด ๆ จะบล็อกการลบดังนั้นมันจึงถูกผูกไว้ว่าจะล้มเหลวไม่ช้าก็เร็ว และคุณไม่สามารถคาดหวังได้ว่าเครื่องมือการค้นหาการสำรองข้อมูลไวรัสและการจัดการไฟล์ของบุคคลที่สามทั้งหมดจะอยู่นอกโฟลเดอร์ของคุณ
Andrey Tarantsov

1
@RubenBartelink อีกหนึ่งตัวอย่างเช่นคุณให้ความล่าช้า 100ms และเวลาล็อคสูงสุดของซอฟต์แวร์ใด ๆ บนพีซีเป้าหมายคือซอฟต์แวร์ AV = 90ms บอกว่ามันยังมีซอฟต์แวร์สำรองข้อมูลที่ล็อคไฟล์เป็นเวลา 70ms ตอนนี้ AV ล็อกไฟล์แอปของคุณรอ 100 มิลลิวินาทีซึ่งปกติแล้วจะดีขึ้น แต่จากนั้นก็พบว่ามีการล็อกอื่นเพราะซอฟต์แวร์สำรองข้อมูลเริ่มทำการจับไฟล์ที่เครื่องหมาย 70ms ของการสแกน AV และจะใช้เวลาอีก 40 มิลลิวินาทีในการปล่อยไฟล์ ดังนั้นในขณะที่ซอฟต์แวร์ AV ใช้เวลานานกว่า & ปกติ 100ms ของคุณจะยาวกว่าหนึ่งในสองแอพ แต่คุณยังคงต้องคำนึงถึงเมื่อมันเริ่มกลาง
vapcguy

2

การลบไดเรกทอรีซ้ำที่ไม่ลบไฟล์นั้นไม่คาดคิด การแก้ไขของฉันสำหรับที่:

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

ผมมีประสบการณ์กรณีที่ช่วยนี้ แต่โดยทั่วไป Directory.Delete ลบไฟล์ภายในไดเรกทอรีเมื่อลบ recursive เป็นเอกสารใน MSDN

บางครั้งฉันพบกับพฤติกรรมที่ผิดปกติเช่นนี้ในฐานะผู้ใช้ Windows Explorer: บางครั้งฉันไม่สามารถลบโฟลเดอร์ได้ (บางครั้งฉันคิดว่าข้อความไร้สาระคือ "การเข้าถึงถูกปฏิเสธ") แต่เมื่อฉันเจาะลึกลงและลบรายการที่ต่ำกว่า รายการเช่นกัน ดังนั้นฉันเดารหัสข้างต้นเกี่ยวกับความผิดปกติของระบบปฏิบัติการ - ไม่ใช่ปัญหาไลบรารีคลาสพื้นฐาน


1

ไดเรกทอรีหรือไฟล์ในนั้นถูกล็อคและไม่สามารถลบได้ ค้นหาผู้ร้ายที่ล็อคและดูว่าคุณสามารถกำจัดมันได้หรือไม่


T1000 ถึงผู้ใช้ที่เปิดโฟลเดอร์: "คุณถูกยกเลิก!"
vapcguy

1

ปรากฏว่าการเลือกพา ธ หรือโฟลเดอร์ย่อยใน Windows Explorer นั้นเพียงพอที่จะบล็อกการเรียกใช้งานไดเรกทอรีเดียวลบ (เส้นทางจริง) แล้วโยน IOException ตามที่อธิบายไว้ข้างต้นและตายแทนการบูต Windows Explorer ไปยังโฟลเดอร์หลักและดำเนินการตาม ที่คาดหวัง


นี่ดูเหมือนจะเป็นปัญหาของฉัน ทันทีที่ฉันปิด Explorer และทำงานอีกครั้งจะไม่มีข้อยกเว้น แม้แต่การเลือกผู้ปกครองของผู้ปกครองก็ยังไม่เพียงพอ ฉันต้องปิด Explorer จริง ๆ
Scott Marlowe

ใช่สิ่งนี้เกิดขึ้นและเป็นสาเหตุ ดังนั้นความคิดใด ๆ เกี่ยวกับวิธีจัดการกับโปรแกรมหรือเป็นคำตอบเพียงเพื่อให้แน่ใจว่าผู้ใช้ทั้งหมด 1,000 คนมีโฟลเดอร์ที่ปิดอยู่?
vapcguy

1

ฉันมีปัญหานี้ในวันนี้ มันเกิดขึ้นเพราะฉันเปิด windows explorer ไปยังไดเรกทอรีที่พยายามจะลบทำให้การเรียกซ้ำเกิดความล้มเหลวและทำให้ IOException ตรวจสอบให้แน่ใจว่าไม่มีหมายเลขอ้างอิงที่เปิดไปยังไดเรกทอรี

นอกจากนี้ MSDN ยังชัดเจนว่าคุณไม่ต้องเขียนการเขียนซ้ำของคุณเอง: http://msdn.microsoft.com/en-us/library/fxeahc5f.aspx


1

ฉันมีปัญหาเดียวกันกับ Windows Workflow Foundation บน build server ที่มี TFS2012 ภายในเวิร์กโฟลว์ที่เรียกว่า Directory.Delete () พร้อมกับตั้งค่าสถานะแบบเรียกซ้ำเป็นจริง ดูเหมือนจะเกี่ยวข้องกับเครือข่ายในกรณีของเรา

เรากำลังลบโฟลเดอร์ไบนารีดร็อปบนเครือข่ายที่ใช้ร่วมกันก่อนที่จะสร้างใหม่และเติมข้อมูลซ้ำด้วยไบนารีล่าสุด การสร้างอื่น ๆ จะล้มเหลว เมื่อเปิดโฟลเดอร์ดรอปหลังจากบิลด์ที่ล้มเหลวโฟลเดอร์ว่างเปล่าซึ่งบ่งชี้ว่าการเรียกใช้ Directory.Delete () ทุกด้านสำเร็จยกเว้นการลบไดเรกทอรีจริง

ปัญหาดูเหมือนจะเกิดจากลักษณะของการสื่อสารไฟล์เครือข่ายแบบอะซิงโครนัส เซิร์ฟเวอร์บิลด์บอกให้ไฟล์เซิร์ฟเวอร์ลบไฟล์ทั้งหมดและไฟล์เซิร์ฟเวอร์รายงานว่ามีไฟล์แม้ว่ามันจะยังไม่เสร็จสิ้นก็ตาม จากนั้นบิลด์เซิร์ฟเวอร์ขอให้ลบไดเรกทอรีและไฟล์เซิร์ฟเวอร์ปฏิเสธการร้องขอเนื่องจากการลบไฟล์ไม่เสร็จสิ้น

โซลูชันที่เป็นไปได้สองแบบในกรณีของเรา:

  • สร้างการลบซ้ำในรหัสของเราเองด้วยความล่าช้าและการตรวจสอบระหว่างแต่ละขั้นตอน
  • ลองใหม่อีกครั้งถึง X ครั้งหลังจาก IOException ทำให้เกิดความล่าช้าก่อนที่จะลองอีกครั้ง

วิธีหลังนั้นรวดเร็วและสกปรก แต่ดูเหมือนว่าจะทำเคล็ดลับ


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 () เพียงแค่ลบไฟล์ทั้งหมดและโฟลเดอร์นั้นแอปก็เริ่มต้นใหม่ด้วยเช่นกัน แต่คุณไม่ได้รับข้อยกเว้นใด ๆเนื่องจากการรีสตาร์ทแอพจะไม่ขัดจังหวะอะไรเลย แต่คุณจะสูญเสียเซสชันที่อยู่ระหว่างดำเนินการแอพไม่ตอบสนองต่อคำขอเมื่อรีสตาร์ทเป็นต้น

เกิดอะไรขึ้น

มีวิธีแก้ไขปัญหาบางอย่างและปรับแต่งเพื่อปิดการใช้งานพฤติกรรมนี้, Junction Directory , การปิด FCN ด้วย Registry , การหยุด FileChangesMonitor โดยใช้ Reflection (เนื่องจากไม่มีวิธีการเปิดเผย)แต่พวกเขาทั้งหมดดูเหมือนจะไม่ถูกต้องเพราะ FCN มีไว้สำหรับ เหตุผล. มันเป็นเรื่องที่มองหาหลังจากที่โครงสร้างของแอปของคุณซึ่งไม่เป็นโครงสร้างของข้อมูลของคุณ คำตอบสั้น ๆ คือ: วางโฟลเดอร์ที่คุณต้องการลบนอกแอพของคุณ FileChangesMonitor จะไม่ได้รับการแจ้งเตือนและแอพของคุณจะไม่ถูกรีสตาร์ททุกครั้ง คุณจะไม่มีข้อยกเว้น ในการทำให้มองเห็นได้จากเว็บมีสองวิธี:

  1. สร้างตัวควบคุมที่จัดการสายเรียกเข้าจากนั้นให้บริการไฟล์กลับโดยอ่านจากโฟลเดอร์นอกแอพ (นอก wwwroot)

  2. หากโครงการของคุณมีขนาดใหญ่และประสิทธิภาพเป็นสิ่งสำคัญที่สุดให้ตั้งค่าเว็บเซิร์ฟเวอร์ขนาดเล็กและรวดเร็วแยกต่างหากสำหรับการให้บริการเนื้อหาคงที่ ดังนั้นคุณจะปล่อยให้ IIS งานเฉพาะของเขา มันอาจจะอยู่ในเครื่องเดียวกัน (พังพอนสำหรับ Windows) หรือเครื่องอื่น (nginx สำหรับ Linux) ข่าวดีก็คือคุณไม่ต้องจ่ายค่าลิขสิทธิ์ Microsoft เพิ่มเติมเพื่อตั้งค่าเซิร์ฟเวอร์เนื้อหาแบบคงที่ใน linux

หวังว่านี่จะช่วยได้


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

ฉันรู้ว่าไม่ได้จัดการกรณีการอนุญาตที่กล่าวถึงในภายหลัง แต่สำหรับ Intent และวัตถุประสงค์ทั้งหมด FAR BETTER จะให้การทำงานที่คาดหวังของไดเรกทอรีต้นฉบับ / หุ้น 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

ข้อควรระวัง: คำตอบนี้ควรใช้กับ recursive = true เท่านั้น เมื่อ false สิ่งนี้จะย้ายไดเร็กทอรีแม้ว่าจะไม่ว่างเปล่า ซึ่งจะเป็นข้อผิดพลาด; พฤติกรรมที่ถูกต้องในกรณีนั้นคือการโยนข้อยกเว้นและออกจากไดเรกทอรีเหมือนเดิม
ToolmakerSteve

1

ปัญหานี้สามารถปรากฏบน Windows เมื่อมีไฟล์ในไดเรกทอรี (หรือในไดเรกทอรีย่อยใด ๆ ) ที่มีความยาวเส้นทางมากกว่า 260 สัญลักษณ์

ในกรณีเช่นนี้คุณจะต้องลบแทน\\\\?\C:\mydir C:\mydirเกี่ยวกับสัญลักษณ์ 260 จำกัด คุณสามารถอ่านได้ที่นี่


1

ฉันแก้ไขด้วยเทคนิคการพัน (คุณสามารถออกจากกระทู้นอนหลับของเขาเองในการจับ)

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 (เรียกซ้ำ: = จริง) อาจทำให้ IOException ซึ่งเกิดจากความล่าช้าในการลบไฟล์


0

ฉันคิดว่ามีไฟล์ที่เปิดโดยสตรีมบางรายการที่คุณไม่ทราบว่าฉันมีปัญหาเดียวกันและแก้ไขได้โดยปิดสตรีมทั้งหมดที่ชี้ไปยังไดเรกทอรีที่ฉันต้องการลบ


0

ข้อผิดพลาดนี้เกิดขึ้นหากไฟล์หรือไดเรกทอรีใด ๆ ถูกพิจารณาว่าใช้งานไม่ได้ มันเป็นข้อผิดพลาดที่ทำให้เข้าใจผิด ตรวจสอบดูว่าคุณมี windows explorer หรือ windows บรรทัดคำสั่งเปิดอยู่ในไดเรกทอรีใด ๆ ในแผนผังหรือโปรแกรมที่ใช้ไฟล์ในแผนผังนั้น


0

ฉันแก้ไขอินสแตนซ์ที่เป็นไปได้หนึ่งรายการของปัญหาที่ระบุไว้เมื่อเมธอดเป็น async และโค้ดแบบนี้:

// 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 มีคำสั่ง if ที่ทำหน้าที่เหมือนกับการใช้คำสั่ง


0

คุณไม่ต้องสร้างและวิธีพิเศษสำหรับการเรียกซ้ำหรือลบไฟล์ภายในโฟลเดอร์พิเศษ ทั้งหมดนี้ทำโดยอัตโนมัติด้วยการโทร

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
    }

ผ่านจริงเป็นตัวแปรเพื่อลบวิธีการจะลบไฟล์ย่อยและโฟลเดอร์ย่อยที่มีไฟล์ด้วย


-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'd คำแนะนำทั่วไปนี้ดีหรือไม่? (ฉันเชื่อว่าคุณเมื่อคุณบอกว่าคุณมีปัญหาและคุณใช้รหัสนี้และคุณรู้สึกว่าคุณไม่มีปัญหาตอนนี้ แต่นั่นไม่ใช่ประเด็น)
Ruben Bartelink

@RubenBartelink ฉันเห็นด้วย มันเป็นแฮ็ค โค้ดวูดูที่ทำบางสิ่งเมื่อมันไม่ชัดเจนว่ามันแก้อะไรหรืออย่างไร ฉันจะรักทางออกที่เหมาะสม
ซ้ำ

1
ปัญหาของฉันคือสิ่งใดก็ตามที่มันเพิ่มขึ้นเรื่อย ๆstackoverflow.com/a/14933880/11635นั้นเป็นการเก็งกำไรอย่างสูง ถ้าฉันทำได้ฉันจะให้ -1 สำหรับการทำซ้ำและ -1 สำหรับการเก็งกำไร / การเขียนโปรแกรมโดยบังเอิญ การโรยGC.Collectเป็น a) เพียงคำแนะนำที่ไม่ดีและ b) ไม่ใช่สาเหตุทั่วไปที่เพียงพอของผู้ที่ถูกล็อคเพื่อทำบุญที่นี่ เพียงเลือกหนึ่งในคนอื่น ๆ และไม่ต้องสับสนในใจของผู้อ่านที่ไร้เดียงสามากขึ้น
Ruben Bartelink

3
ใช้ GC.WaitForPendingFinalizers (); หลังจาก GC.Collect (); สิ่งนี้จะทำงานได้ตามที่คาดไว้
Heiner

ไม่แน่ใจไม่ได้ทดสอบ แต่อาจจะดีกว่าที่จะทำสิ่งที่มี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

-3

เพิ่มจริงในพารามิเตอร์ที่สอง

Directory.Delete(path, true);

มันจะลบทั้งหมด


1
Directory.Delete (เส้นทาง, จริง); เป็นคำถามเดิม
Pisu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.