如何避免多用户同时执行C#脚本特定代码段引发异常?
并发风险发生概率及解决办法
一、发生概率
在多用户并发触发这段代码的场景下,异常发生概率并不低——尤其是当H盘为机械硬盘、网络共享盘这类IO性能较弱的存储介质时,概率会显著升高。因为IO操作耗时远大于CPU判断逻辑,两个线程很容易在「判断目录不存在」到「完成目录创建+写入1.cms」的间隙同时进入分支,导致1.cms被覆盖;或是在统计文件数量到写入新文件的过程中,另一个线程已写入新文件,引发重复编号的文件冲突。如果用户操作密集,这类问题几乎必然会触发。
二、解决办法
方案1:使用跨进程互斥锁串行化操作
针对每个doc_number创建唯一的命名互斥锁,确保同一文档的操作被串行执行,不同文档的操作互不影响,这是解决并发冲突最直接的方式:
string mutexName = $"Doc_{doc_number}_Mutex"; using (var mutex = new Mutex(false, mutexName)) { try { // 等待获取锁,超时时间可根据实际情况调整 if (mutex.WaitOne(TimeSpan.FromSeconds(10))) { string dirPath = Path.Combine(@"H:\", doc_number, "detached"); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); File.WriteAllBytes(Path.Combine(dirPath, "1.cms"), signature_bytes); } else { int filesNumber = Directory.GetFiles(dirPath).Length; string newFilePath = Path.Combine(dirPath, $"{filesNumber + 1}.cms"); File.WriteAllBytes(newFilePath, signature_bytes); } } else { throw new TimeoutException("获取操作锁超时,请稍后重试"); } } finally { mutex.ReleaseMutex(); } }
方案2:使用唯一文件名规避编号竞争
放弃按顺序编号的方式,改用全局唯一的文件名(比如GUID),从根源上避免文件编号冲突,这种方式性能更好,无需等待锁:
string dirPath = Path.Combine(@"H:\", doc_number, "detached"); // CreateDirectory若目录已存在不会抛出异常,直接跳过创建逻辑 Directory.CreateDirectory(dirPath); // 使用GUID保证文件名全局唯一 string fileName = $"{Guid.NewGuid().ToString("N")}.cms"; string newFilePath = Path.Combine(dirPath, fileName); File.WriteAllBytes(newFilePath, signature_bytes);
如果需要保留顺序记录,后续可通过文件的创建时间排序,而非依赖文件名编号。
方案3:利用文件系统原子写入特性(兜底方案)
通过FileStream的原子写入选项尝试写入文件,捕获文件已存在的异常后重试,适合无法使用锁且需要保留编号的场景:
string dirPath = Path.Combine(@"H:\", doc_number, "detached"); Directory.CreateDirectory(dirPath); int retryCount = 3; while (retryCount > 0) { try { int filesNumber = Directory.GetFiles(dirPath).Length; string newFilePath = Path.Combine(dirPath, $"{filesNumber + 1}.cms"); // 使用FileMode.CreateNew确保文件不存在时才写入,原子性写入 using (var fs = new FileStream(newFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fs.Write(signature_bytes, 0, signature_bytes.Length); } break; } catch (IOException) { retryCount--; Thread.Sleep(100); } } if (retryCount == 0) { throw new IOException("多次尝试写入文件失败,请稍后重试"); }
内容的提问来源于stack exchange,提问作者Malyshev Artem
相关产品推荐
相关产品推荐

