FileStream创建文件时Sharing violation IOException问题排查与解决
问题原因分析
共享冲突IOException的原因
虽然每个线程操作的是不同文件名,但同一目录的元数据(如目录项、文件分配表)是共享资源。当多个线程同时在该目录下创建文件时,Windows系统需要更新目录的元数据,这个过程如果没有同步,会触发系统级的共享冲突,抛出IOException: Sharing violation。这种情况和单个文件的读写冲突无关,而是目录层面的操作竞态导致的。
ReaderWriterLockSlim导致死锁的原因
ReaderWriterLockSlim是线程绑定的锁,它要求获取锁和释放锁必须在同一个线程上。但你的代码中包含异步操作(如await _httpClient.GetAsync、await response.Content.CopyToAsync),await会释放当前线程,后续代码恢复时可能在另一个线程执行。这会导致:
- 锁被持有在原线程,但后续的
ExitWriteLock在新线程执行,无法正确释放锁,导致其他线程永久等待锁,引发死锁。 - 锁的持有时间被无限拉长(包含HTTP请求的耗时),大幅增加了死锁的概率。
解决方案
1. 使用支持异步的锁:SemaphoreSlim
替换ReaderWriterLockSlim为SemaphoreSlim,它支持异步场景,且不与线程绑定。示例代码如下:
// 静态声明,限制同时操作目录的线程数,设为1确保串行创建文件 private static readonly SemaphoreSlim _directoryLock = new SemaphoreSlim(1, 1); public async Task ProcessFileAsync(string url, string pathToNewFile, string name, string filepath, string directoryName) { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); HttpResponseMessage response = null; try { var uri = new Uri(url); response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token); response.EnsureSuccessStatusCode(); // 仅在创建文件时加锁,缩小锁范围 await _directoryLock.WaitAsync(cancellationTokenSource.Token); try { await using (var fs = new FileStream(Path.Combine(pathToNewFile, $"{name}.zip"), FileMode.Create)) { await response.Content.CopyToAsync(fs, cancellationTokenSource.Token); } } finally { _directoryLock.Release(); } // 读取和解压操作不需要锁,文件已完全写入且文件名唯一 using (var stream = File.OpenRead(filepath)) using (var zipArchive = new ZipArchive(stream, ZipArchiveMode.Read)) { if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } zipArchive.ExtractToDirectory(directoryName, true); } } catch (Exception ex) { taskCompletionSource?.TrySetException(ex); } finally { response?.Dispose(); cancellationTokenSource?.Dispose(); } }
2. 优化锁的范围
只对目录下创建文件的操作加锁,HTTP请求、文件读取解压等操作不需要锁,这样可以大幅缩短锁的持有时间,减少线程阻塞的概率。
3. 使用更安全的文件创建API(可选)
在.NET Core 3.0+或.NET 5+中,可以使用File.CreateAsync替代FileStream,它是异步原生API,可能进一步降低系统层面的冲突概率:
await using (var fs = await File.CreateAsync(Path.Combine(pathToNewFile, $"{name}.zip"), cancellationTokenSource.Token)) { await response.Content.CopyToAsync(fs, cancellationTokenSource.Token); }
内容的提问来源于stack exchange,提问作者Emil
相关产品推荐
相关产品推荐

