You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免多用户同时执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 05:27:24