我在一个针对.Net 4.5.2的单线程进程中运行了一些代码。此代码创建一个文件,执行一些操作然后删除该文件。大多数时候这种方法都很好,但有时候会抛出异常。
我试图简化并重新创建下面的问题。从截图中可以看出,它设法创建和删除文件240次以上,然后使用UnauthorizedAccessException失败。有时下面的代码运行没有错误,有时它会越来越循环。那么这里发生了什么?
在生产环境中,文件被写入网络共享或从网络共享中删除,当使用网络共享时,抛出IOException如下(因此它在稍微不同的地方中断,在File.Delete期间而不是): / p>
System.IO.IOException: The process cannot access the file '\\network_location\sub_dir\TMP1.sql' because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalDelete(String path, Boolean checkHost)
据我了解,using语句应调用dispose,导致文件流被刷新;所以在using语句之后我希望所有的数据都写到磁盘上。
我如何确保这一点能够像我期望的那样始终如一?
static void Main(string[] args)
{
string networkFilePath = @"C:\Temp";
string filename1 = "tmp1.sql";
string filename2 = "tmp2.sql";
string tempFilename1 = Path.Combine(networkFilePath, filename1);
string tempFilename2 = Path.Combine(networkFilePath, filename2);
int interations = 20000;
Console.WriteLine("Started");
try
{
for (int i = 0; i < interations; i++)
{
using (var s = new StreamWriter(tempFilename1))
{
s.Write("aaa");
}
using (var s = new StreamWriter(tempFilename2))
{
s.Write("aaa");
}
FileCompare(tempFilename1, tempFilename2);
File.Delete(tempFilename1);
File.Delete(tempFilename2);
}
}
catch (IOException e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Finished");
Console.ReadLine();
}
public static bool FileCompare(string filename1, string filename2)
{
using (var fs1 = new FileStream(filename1, FileMode.Open))
using (var fs2 = new FileStream(filename2, FileMode.Open))
{
return [... ommited for brevity, but just checks the content of file1 vs file2]
}
}
修改 我知道我可以使用临时文件名来避免这个问题...问题是,我如何确定IO操作是否完整,例如,缓存数据是在本地还是在网络上写入磁盘?
答案 0 :(得分:1)
大多数情况下,当某个其他程序使用包含FileShare.Delete
的许可共享模式打开同一文件时,会发生这种情况。这个共享允许另一个进程删除一个文件,但是在关闭它的所有句柄之前不会真正删除文件(包括用FileShare.Delete
打开它的进程获得的句柄)。此类程序的示例可能是搜索索引器和防病毒软件 - 两者都可能希望扫描您创建的新文件,并且都不想锁定文件,因此将以非常宽松的共享模式打开它,包括FileShare.Delete
。
当您使用File.Delete
打开的文件上调用FileShare.Delete
时,它将返回而没有任何错误,文件将被标记为删除(并将在所有句柄关闭后删除)。任何后续尝试访问此类“已删除”文件都将抛出您在示例中观察到的访问被拒绝异常。
您可以使用以下代码轻松复制该内容:
static void Main(string[] args) {
var tmpFile = Path.GetTempFileName();
// file is now created
new Thread(() => {
using (var fs = new FileStream(tmpFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) {
// open with permissive share mode, including FileShare.Delete
// and hold that handle for 5 seconds
Thread.Sleep(5000);
}
}).Start();
// wait a bit for thread above to grab a handle
Thread.Sleep(1000);
// delete, will return fine without any errors
File.Delete(tmpFile);
// attempt to create - will fail with access denied exception you have
using (var s = new StreamWriter(tmpFile)) {
s.Write("aaa");
}
Console.WriteLine("done");
Console.ReadLine();
}
因为你不知道另一个进程会持有多长时间文件句柄你要删除 - 我认为你所能做的只是检查文件在删除后是否仍然存在,如果是 - 请稍等一下再检查一下:
File.Delete(tmpFile);
while (File.Exists(tmpFile)) {
// obviously don't loop forever
// check for some time then throw exception
Thread.Sleep(100);
}
// in the example above it will loop for 4 seconds while
// handle is hold by another thread, then finally return
// and subsequent creation of new file will work fine without exceptions
答案 1 :(得分:0)
当拖放已评论,添加.Close()
时,应该真正调用它。只是为了确保流已关闭。
如果没有这个技巧,你可以使用随机或增加(1,2,3,...)文件名,因为它们无论如何都是临时的。对这些文件进行排队并让第二个线程尝试删除它们,直到成功为止。
答案 2 :(得分:0)
有时除了操作系统或GC都没有控制文件句柄,这两者都是你无法控制的,并且不值得监视I / O,...你的目标似乎是可靠的清理,而不是我/ O监控。
如果您想采用线程方法,可以将这些文件路径排队到spinwait线程以进行异常容错清理。
如果你不想添加一个线程,那么在异常时退出清除循环&#39;可以放在处理周期的底部。
无论哪种方式,您都可以回答自己适当的方法来处理那些顽固拒绝删除的文件。考虑以下伪代码方法,将要清理的文件排队到跟踪删除重试的字典中以避免无限循环:
// ... declarative section, downgrade to Dictionary class if thread-safe concurrency is not needed
ConcurrentDictionary<string, int> files_for_cleanup = new ConcurrentDictionary<string, int>();
// ... bottom of your processing loop
if (!files_for_cleanup.ContainsKey(current_temp_file))
{
files_for_cleanup.TryAdd(current_temp_file, 0);
}
// ... switching to cleanup thread, if threaded, otherwise continue with the bottom of your processing loop
KeyValuePair<string, int> item = null;
try
{
while(!files_for_cleanup.IsEmpty)
{
item = files_for_cleanup.First();
if(File.Exists(item.Key)
{
File.Delete(item.Key);
}
files_for_cleanup.TryRemove(item.Key, item.Value);
}
}
catch(Exception)
{
if (item != null)
{
if (item.Value > 40 /* retry threshold */)
{
files_for_cleanup.TryRemove(item.Key, item.Value);
// ... perform other unrecoverable actions, e.g. logging, delete on reboot, ...
}
else
{
files_for_cleanup.TryUpdate(item.Key, item.Value, item.Value + 1);
}
}
}
// ... if in thread, spinwait, sleep, etc. otherwise return to top of loop