如何等待文件空闲,以便ss.Save()
可以用新的文件覆盖它。如果我两次靠近运行(ish),我会收到generic GDI+
错误。
///<summary>
/// Grabs a screen shot of the App and saves it to the C drive in jpg
///</summary>
private static String GetDesktopImage(DevExpress.XtraEditors.XtraForm whichForm)
{
Rectangle bounds = whichForm.Bounds;
// This solves my problem but creates a clutter issue
//var timeStamp = DateTime.Now.ToString("ddd-MMM-dd-yyyy-hh-mm-ss");
//var fileName = "C:\\HelpMe" + timeStamp + ".jpg";
var fileName = "C:\\HelpMe.jpg";
File.Create(fileName);
using (Bitmap ss = new Bitmap(bounds.Width, bounds.Height))
using (Graphics g = Graphics.FromImage(ss))
{
g.CopyFromScreen(whichForm.Location, Point.Empty, bounds.Size);
ss.Save(fileName, ImageFormat.Jpeg);
}
return fileName;
}
答案 0 :(得分:54)
这样的功能可以做到:
public static bool IsFileReady(string filename)
{
// If the file can be opened for exclusive access it means that the file
// is no longer locked by another process.
try
{
using (FileStream inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
return inputStream.Length > 0;
}
catch (Exception)
{
return false;
}
}
将其粘贴在while
循环中,您可以阻止该文件被访问:
public static void WaitForFile(string filename)
{
//This will lock the execution until the file is ready
//TODO: Add some logic to make it async and cancelable
while (!IsFileReady(filename)) { }
}
答案 1 :(得分:13)
如果在写入文件之前检查访问权限,则某些其他进程可能会在您进行写入之前再次获取访问权限。因此我建议以下两种之一:
获取信息流
private FileStream GetWriteStream(string path, int timeoutMs)
{
var time = Stopwatch.StartNew();
while (time.ElapsedMilliseconds < timeoutMs)
{
try
{
return new FileStream(path, FileMode.Create, FileAccess.Write);
}
catch (IOException e)
{
// access error
if (e.HResult != -2147024864)
throw;
}
}
throw new TimeoutException($"Failed to get a write handle to {path} within {timeoutMs}ms.");
}
然后像这样使用它:
using (var stream = GetWriteStream("path"))
{
using (var writer = new StreamWriter(stream))
writer.Write("test");
}
重试范围
private void WithRetry(Action action, int timeoutMs = 1000)
{
var time = Stopwatch.StartNew();
while(time.ElapsedMilliseconds < timeoutMs)
{
try
{
action();
return;
}
catch (IOException e)
{
// access error
if (e.HResult != -2147024864)
throw;
}
}
throw new Exception("Failed perform action within allotted time.");
}
然后使用 WithRetry(()=&gt; File.WriteAllText(Path.Combine(_directory,name),contents));
答案 2 :(得分:3)
没有任何功能可以让您等待特定的句柄/文件系统位置可供写入。可悲的是,你所能做的只是轮询句柄进行写作。
答案 3 :(得分:3)
这是一个可能对某些用户来说过度杀伤的解决方案。我创建了一个新的静态类,它有一个仅在文件完成复制时触发的事件。
用户通过调用FileAccessWatcher.RegisterWaitForFileAccess(filePath)
来注册他们想要观看的文件。如果文件尚未被监视,则启动新任务,重复检查文件以查看是否可以打开该文件。每次检查时它也会读取文件大小。如果文件大小没有在预定义的时间内增加(在我的示例中为5分钟),则退出循环。
当循环退出可访问的文件或超时时,会触发FileFinishedCopying
事件。
public class FileAccessWatcher
{
// this list keeps track of files being watched
private static ConcurrentDictionary<string, FileAccessWatcher> watchedFiles = new ConcurrentDictionary<string, FileAccessWatcher>();
public static void RegisterWaitForFileAccess(string filePath)
{
// if the file is already being watched, don't do anything
if (watchedFiles.ContainsKey(filePath))
{
return;
}
// otherwise, start watching it
FileAccessWatcher accessWatcher = new FileAccessWatcher(filePath);
watchedFiles[filePath] = accessWatcher;
accessWatcher.StartWatching();
}
/// <summary>
/// Event triggered when the file is finished copying or when the file size has not increased in the last 5 minutes.
/// </summary>
public static event FileSystemEventHandler FileFinishedCopying;
private static readonly TimeSpan MaximumIdleTime = TimeSpan.FromMinutes(5);
private readonly FileInfo file;
private long lastFileSize = 0;
private DateTime timeOfLastFileSizeIncrease = DateTime.Now;
private FileAccessWatcher(string filePath)
{
this.file = new FileInfo(filePath);
}
private Task StartWatching()
{
return Task.Factory.StartNew(this.RunLoop);
}
private void RunLoop()
{
while (this.IsFileLocked())
{
long currentFileSize = this.GetFileSize();
if (currentFileSize > this.lastFileSize)
{
this.lastFileSize = currentFileSize;
this.timeOfLastFileSizeIncrease = DateTime.Now;
}
// if the file size has not increased for a pre-defined time limit, cancel
if (DateTime.Now - this.timeOfLastFileSizeIncrease > MaximumIdleTime)
{
break;
}
}
this.RemoveFromWatchedFiles();
this.RaiseFileFinishedCopyingEvent();
}
private void RemoveFromWatchedFiles()
{
FileAccessWatcher accessWatcher;
watchedFiles.TryRemove(this.file.FullName, out accessWatcher);
}
private void RaiseFileFinishedCopyingEvent()
{
FileFinishedCopying?.Invoke(this,
new FileSystemEventArgs(WatcherChangeTypes.Changed, this.file.FullName, this.file.Name));
}
private long GetFileSize()
{
return this.file.Length;
}
private bool IsFileLocked()
{
try
{
using (this.file.Open(FileMode.Open)) { }
}
catch (IOException e)
{
var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
return errorCode == 32 || errorCode == 33;
}
return false;
}
}
使用示例:
// register the event
FileAccessWatcher.FileFinishedCopying += FileAccessWatcher_FileFinishedCopying;
// start monitoring the file (put this inside the OnChanged event handler of the FileSystemWatcher
FileAccessWatcher.RegisterWaitForFileAccess(fileSystemEventArgs.FullPath);
处理FileFinishedCopyingEvent:
private void FileAccessWatcher_FileFinishedCopying(object sender, FileSystemEventArgs e)
{
Console.WriteLine("File finished copying: " + e.FullPath);
}
答案 4 :(得分:2)
bool isLocked = true;
while (isLocked)
try {
System.IO.File.Move(filename, filename2);
isLocked = false;
}
catch { }
System.IO.File.Move(filename2, filename);
答案 5 :(得分:2)
您可以让系统等待,直到该过程关闭。
就像这样简单:
Process.Start("the path of your text file or exe").WaitForExit();
答案 6 :(得分:1)
使用@Gordon Thompson的答案,你必须创建一个循环,如下面的代码:
public static bool IsFileReady(string sFilename)
{
try
{
using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None))
return inputStream.Length > 0;
}
catch (Exception)
{
return false;
}
}
while (!IsFileReady(yourFileName)) ;
我找到了一种不会导致CPU开销的优化方式:
public static bool IsFileReady(this string sFilename)
{
try
{
using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None))
return inputStream.Length > 0;
}
catch (Exception)
{
return false;
}
}
SpinWait.SpinUntil(yourFileName.IsFileReady);
答案 7 :(得分:0)
你可以使用带有Dummy变量的lock语句,它看起来效果很好。
检查here。
答案 8 :(得分:0)
我用最重要的答案写了一个类似的答案,但是它是异步的,非阻塞的,可等待的,可取消的(只需停止任务即可)并检查抛出的异常。
public static async Task IsFileReady(string filename)
{
await Task.Run(() =>
{
if (!File.Exists(path))
{
throw new IOException("File does not exist!");
}
var isReady = false;
while (!isReady)
{
// If the file can be opened for exclusive access it means that the file
// is no longer locked by another process.
try
{
using (FileStream inputStream =
File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
isReady = inputStream.Length > 0;
}
catch (Exception e)
{
// Check if the exception is related to an IO error.
if (e.GetType() == typeof(IOException))
{
isReady = false;
}
else
{
// Rethrow the exception as it's not an exclusively-opened-exception.
throw;
}
}
}
});
}
您可以通过以下方式使用它:
Task ready = IsFileReady(path);
ready.Wait(1000);
if (!ready.IsCompleted)
{
throw new FileLoadException($"The file {path} is exclusively opened by another process!");
}
File.Delete(path);
如果您必须真正等待它,或者以更JS的承诺方式:
IsFileReady(path).ContinueWith(t => File.Delete(path));
答案 9 :(得分:0)
问题是您的代码已经通过调用File.Create
打开了文件,该文件返回打开的文件流。根据时间的不同,垃圾收集器可能已经注意到返回的流没有被使用,并将其放在终结器队列中,然后终结器线程可能已经清理了一切,然后再次开始写入文件。但是,正如您所注意到的,这不能保证。
要解决此问题,您可以立即像File.Create(...).Dispose()
一样再次关闭文件。或者,将流包装在using语句中,然后写入。
using (FileStream stream = File.Create(fileName))
using (Bitmap ss = new Bitmap(bounds.Width, bounds.Height))
using (Graphics g = Graphics.FromImage(ss))
{
g.CopyFromScreen(whichForm.Location, Point.Empty, bounds.Size);
ss.Save(stream, ImageFormat.Jpeg);
}
答案 10 :(得分:0)
我使用的一种做法是在文件的字符串末尾写一个特定的单词。 输入“退出”。 然后检查读取的字符串是否以单词“ Exit”结尾意味着文件已被完全读取。