怪奇物语

怪奇物语

首页 新随笔 联系 管理

using System.Collections.Concurrent;
using System.Diagnostics;

// 发布命令 (不包含调试信息)
// dotnet publish .\CopyFile\CopyFile.csproj -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:DebugType=None -o .\Publish\CopyFile\V1.0.0

// 发布命令(包含调试信息)
// dotnet publish .\CopyFile\CopyFile.csproj -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true  -o .\Publish\CopyFile\V2.0.0

// 命令行运行:
// set "scriptPath=%cd%"
// dotnet run --project ./CopyFile/CopyFile.csproj -- "%scriptPath%"

public class Program
{
    private static string SourceDirectory = string.Empty;
    private static string DstFolderPath = string.Empty;
    private static string SourceDirectoryBakPath = string.Empty;
    private static string DirectorySuffix = string.Empty;
    private static readonly DateTime Now = DateTime.Now;
    private static readonly List<string> ExcludedEqualDirectories = new List<string>
    {
        "bin",
        "obj",
        "npm_modules",
        ".git",
        "Publish",
    };

    // ⭐ 优化点 1:使用 BlockingCollection 代替 ConcurrentQueue
    private static readonly BlockingCollection<CopyFileTask> BackupCollection =
        new BlockingCollection<CopyFileTask>();

    private static volatile bool IsProducingComplete = false;
    private static readonly ConcurrentDictionary<string, object> DirectoryLocks =
        new ConcurrentDictionary<string, object>();

    private static readonly CancellationTokenSource CancellationTokenSource =
        new CancellationTokenSource();

    static void Main(string[] args)
    {
        try
        {
            // 扫描源目录并准备移动任务
            Console.WriteLine("正在扫描文件...");
            System.Console.WriteLine("输入文件夹后缀:");
            DirectorySuffix = System.Console.ReadLine() ?? string.Empty;
            if (args.Length == 0)
            {
                System.Console.WriteLine("请传入文件夹路径");
                return;
            }
            SourceDirectory = args[0];
            // 检查源目录是否存在
            if (!Directory.Exists(SourceDirectory))
            {
                Console.WriteLine($"错误: 源目录 {SourceDirectory} 不存在。");
                return;
            }

            System.Console.WriteLine("请输入目标文件夹:");
            DstFolderPath = System.Console.ReadLine() ?? string.Empty;
            if (DstFolderPath == string.Empty)
                return;

            SourceDirectoryBakPath =
                Path.GetFileName(SourceDirectory) + $"_{Now:yyyyMMdd_HHmmss}_{DirectorySuffix}";

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            var producerTask = Task.Run(() =>
            {
                try
                {
                    var options = new ParallelOptions
                    {
                        MaxDegreeOfParallelism = Environment.ProcessorCount * 2,
                    };
                    // 使用 Parallel.ForEach 对顶层目录进行并行递归扫描
                    Parallel.ForEach(
                        Directory.EnumerateDirectories(SourceDirectory),
                        options,
                        (dirPath) =>
                        {
                            ScanDirectory(dirPath);
                        }
                    );
                    // 单独处理根目录下的文件
                    GetFiles(SourceDirectory);
                }
                finally
                {
                    // ⭐ 关键:当所有生产者都完成后,标记集合已“完成添加”
                    // 这样消费者就知道不会再有新任务了
                    BackupCollection.CompleteAdding();
                    IsProducingComplete = true;
                }
            });

            Console.WriteLine("扫描已启动,开始并行处理文件...");
            // 启动多个工作线程
            const int maxThreads = 8;
            var backupTasks = new Task[maxThreads];
            for (int i = 0; i < maxThreads; i++)
            {
                backupTasks[i] = Task.Run(() => CodeBackup(CancellationTokenSource.Token));
            }
            // 等待所有任务完成
            Task.WaitAll(backupTasks);
            stopwatch.Stop();
            Console.WriteLine($"处理完成,共耗时 {stopwatch.Elapsed.TotalSeconds} s");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"\n发生致命错误: {ex.Message}");
        }
        finally
        {
            CancellationTokenSource.Cancel();
        }
        Console.WriteLine("============备份完成============");
    }

    private static bool FilterFolder(string dirPath)
    {
        var dirName = Path.GetFileName(dirPath);
        var excludedSet = new HashSet<string>(
            ExcludedEqualDirectories,
            StringComparer.OrdinalIgnoreCase
        );
        return !excludedSet.Contains(dirName);
    }

    private static void ScanDirectory(string currentPath)
    {
        if (!FilterFolder(currentPath))
            return;
        try
        {
            // 处理当前目录的文件
            GetFiles(currentPath);
            var options = new ParallelOptions
            {
                MaxDegreeOfParallelism = Environment.ProcessorCount * 2,
            };
            // ⭐ 关键:对子目录的遍历也使用并行方式
            Parallel.ForEach(
                Directory.EnumerateDirectories(currentPath),
                options,
                (subDirectory) =>
                {
                    ScanDirectory(subDirectory); // 递归调用
                }
            );
        }
        catch (IOException ex)
        {
            Console.WriteLine($"IOException ex: {ex.Message}");
        }
        catch (UnauthorizedAccessException ex)
        { /* 忽略无权限目录 */
            System.Console.WriteLine($"UnauthorizedAccessException ex: {ex.Message} ");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"\n扫描目录时出错 ({currentPath}): {ex.Message}");
        }
    }

    private static void GetFiles(string sourcePath)
    {
        var files = Directory.GetFiles(sourcePath, "*", SearchOption.TopDirectoryOnly);
        foreach (string filePath in files)
        {
            string relativePath = Path.GetRelativePath(SourceDirectory, filePath);
            string dstCodePath = Path.Combine(DstFolderPath, SourceDirectoryBakPath, relativePath);
            EnsureDirectoryExists(Path.GetDirectoryName(dstCodePath)!);
            var task = new CopyFileTask { CodePath = filePath, DstCodePath = dstCodePath };
            BackupCollection.Add(task);
        }
    }

    private static void CodeBackup(CancellationToken cancellationToken)
    {
        try
        {
            // GetConsumingEnumerable 会阻塞线程直到有新项目或集合被标记为完成
            foreach (var copyFileTask in BackupCollection.GetConsumingEnumerable(cancellationToken))
            {
                try
                {
                    CopyFile(copyFileTask);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"\n处理文件时出错 ({copyFileTask.CodePath}): {ex.Message}");
                }
            }
        }
        catch (OperationCanceledException)
        {
            // 如果取消操作被请求,循环会正常退出
            System.Console.WriteLine("\n文件处理已取消。");
        }
    }

    private static void CopyFile(CopyFileTask code2mdTask, int maxRetries = 3)
    {
        int retries = 0;
        while (true)
        {
            try
            {
                string codePath = code2mdTask.CodePath;
                string dstCodePath = code2mdTask.DstCodePath;
                File.Copy(codePath, dstCodePath, false);
                System.Console.WriteLine(codePath + "=>" + dstCodePath);
                // 验证文件是否成功移动
                if (!File.Exists(dstCodePath))
                {
                    throw new IOException("文件复制后不存在于目标位置");
                }

                break;
            }
            catch (Exception ex)
            {
                retries++;
                if (retries > maxRetries)
                {
                    Console.WriteLine($"文件移动失败 ({code2mdTask.CodePath} : {ex.Message}");
                    throw;
                }
                // 等待一段时间后重试
                Thread.Sleep(500 * retries);
            }
        }
    }

    // ⭐ 新增:一个执行绪安全的目录建立辅助方法
    private static void EnsureDirectoryExists(string directoryPath)
    {
        if (Directory.Exists(directoryPath))
            return;
        // 为目录路径取得或新增一个锁
        object dirLock = DirectoryLocks.GetOrAdd(directoryPath, _ => new object());
        if (!Directory.Exists(directoryPath))
        {
            if (Directory.Exists(directoryPath))
                return;
            lock (dirLock)
            {
                Directory.CreateDirectory(directoryPath);
            }
        }
    }
}

public class CopyFileTask
{
    public string CodePath { get; set; }
    public string DstCodePath { get; set; }
}


posted on 2025-07-26 08:00  超级无敌美少男战士  阅读(7)  评论(0)    收藏  举报