System.IO.File.Move--如何等待移动完成?

12

我正在使用C#编写一个WPF应用程序,需要移动一些文件。问题是,我非常需要知道这些文件是否成功移动到目标目录。为了解决这个问题,我编写了一个检查程序来确保在移动后文件能够成功到达目标目录。但有时候我会在文件移动完成之前就执行检查程序,这是一个麻烦的问题。

   System.IO.File.Move(file.FullName, endLocationWithFile);

            System.IO.FileInfo[] filesInDirectory = endLocation.GetFiles();
            foreach (System.IO.FileInfo temp in filesInDirectory)
            {
                if (temp.Name == shortFileName)
                {

                    return true;
                }
            }

            // The file we sent over has not gotten to the correct   directory....something went wrong!
            throw new IOException("File did not reach destination");

        }
        catch (Exception e)
        {
            //Something went wrong, return a fail;
            logger.writeErrorLog(e);
            return false;
        }

有人能告诉我如何确保文件实际上已到达目的地吗?-我将要移动的文件可能非常大 - (长达2小时的全高清mp4文件)

谢谢!


2
你如何通过复制流而不是使用 Move 来自行管理移动呢?这样你就能确切地了解正在发生的事情。 - spender
听起来不错...你能发一个链接,提供更详细的如何操作的信息吗? - Mizmor
我添加了一个答案来帮助你。 - spender
7个回答

10

您可以使用 AsyncAwait 流来确保文件被完全复制。

以下代码应该可以正常工作:

private void Button_Click(object sender, RoutedEventArgs e)
{
    string sourceFile = @"\\HOMESERVER\Development Backup\Software\Microsoft\en_expression_studio_4_premium_x86_dvd_537029.iso";
    string destinationFile = "G:\\en_expression_studio_4_premium_x86_dvd_537029.iso";

    MoveFile(sourceFile, destinationFile);
}

private async void MoveFile(string sourceFile, string destinationFile)
{
    try
    {
        using (FileStream sourceStream = File.Open(sourceFile, FileMode.Open))
        {
            using (FileStream destinationStream = File.Create(destinationFile))
            {
                await sourceStream.CopyToAsync(destinationStream);
                if (MessageBox.Show("I made it in one piece :), would you like to delete me from the original file?", "Done", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                {
                    sourceStream.Close();
                    File.Delete(sourceFile);
                }
            }
        }
    }
    catch (IOException ioex)
    {
        MessageBox.Show("An IOException occured during move, " + ioex.Message);
    }
    catch (Exception ex)
    {
        MessageBox.Show("An Exception occured during move, " + ex.Message);
    }
}

如果您正在使用VS2010,您需要安装异步 CTP才能使用新的Async/Await语法。


这是一个非常干净的解决方案 - 谢谢! - Mizmor
为什么你的 if 语句在 using 块内部。也就是说,为什么不将 if (MessageBox.Show(...)) File.Delete(sourceFile); 放在两个 using 块之外呢?(最好还要包括一个 bool deleteSource = true 参数。) - Keith Robertson
为什么这很重要呢?两种方式的效果都是一样的,using 语句内部的 if 不会有任何影响,using 语句只是用来确保流被正确释放,与 if 语句无关。你可以用100种不同的方式编写代码,但效果都是相同的。 - sa_ddam213
当我们尝试在并行处理中使用上述代码时,会出现“文件被另一个进程使用”的错误。请帮我处理这个错误。 代码:Parallel.ForEach(ds.Tables [0] .AsEnumerable(),row => { //循环挑选每个文件并移动到另一个文件夹。} - hmk

1
为什么不通过复制流来自己管理副本呢?
//http://www.dotnetthoughts.net/writing_file_with_non_cache_mode_in_c/
const FileOptions FILE_FLAG_NO_BUFFERING = (FileOptions) 0x20000000;

//experiment with different buffer sizes for optimal speed
var bufLength = 4096;

using(var outFile = 
    new FileStream(
        destPath,
        FileMode.Create, 
        FileAccess.Write, 
        FileShare.None, 
        bufLength, 
        FileOptions.WriteThrough | FILE_FLAG_NO_BUFFERING))
using(var inFile = File.OpenRead(srcPath))
{
    //either
    //inFile.CopyTo(outFile);

    //or
    var fileSizeInBytes = inFile.Length;
    var buf = new byte[bufLength];
    long totalCopied = 0L;
    int amtRead;
    while((amtRead = inFile.Read(buf,0,bufLength)) > 0)
    {
        outFile.Write(buf,0,amtRead);
        totalCopied += amtRead;
        double progressPct = 
            Convert.ToDouble(totalCopied) * 100d / fileSizeInBytes;
        progressPct.Dump();
    }
}
//file is written

1

您可以观察文件是否从原始目录中消失,然后确认它们确实出现在目标目录中。

我对文件监视器没有太好的经验。我可能会让执行移动操作的线程等待AutoResetEvent,同时另一个线程或计时器定期检查文件是否从原始位置消失,并检查它们是否在新位置,根据您的环境和需求,可能还会执行一致性检查(例如MD5检查)。一旦满足这些条件,“检查器”线程/计时器将触发AutoResetEvent,以便原始线程可以继续执行。

在“检查器”中包含一些“这需要太长时间”的逻辑。


0

最近遇到了类似的问题。

OnBackupStarts();
//.. do stuff

 new TaskFactory().StartNew(() =>
                {
                    OnBackupStarts()
                    //.. do stuff
                    OnBackupEnds();
                });


void OnBackupEnds()
    {
        if (BackupChanged != null)
        {
            BackupChanged(this, new BackupChangedEventArgs(BackupState.Done));
        }
    }

不要等待,对事件做出反应

0

你最好将移动操作放在一个单独的线程中,这样你就不会让应用程序执行停止数小时。

如果程序不能在移动完成之前继续运行,那么你可以打开一个对话框并定期检查移动线程以更新进度跟踪器。这为用户提供了反馈,并防止他们感觉程序已经冻结。

这里有相关信息和示例: http://hintdesk.com/c-wpf-copy-files-with-progress-bar-by-copyfileex-api/


0

尝试在后台任务中定期检查复制文件的大小是否达到原始文件的大小(您可以添加比较文件之间的哈希值)


0
首先要注意,操作系统中移动文件并不是“重新创建”文件到新目录,而只是在“文件分配表”中更改其位置数据,因为复制所有字节以删除旧的字节只是浪费时间。
由于这个原因,移动文件是一个非常快速的过程,无论文件大小如何。
编辑:正如Mike Christiansen在评论中所述,当文件在同一卷内移动时(你知道的,从C:\...到C:\...),这个“快速”的过程才会发生。
因此,像“sa_ddam213”在他的回答中建议的复制/删除行为可以工作,但并不是最佳解决方案(需要更长的完成时间,如果例如你没有足够的自由磁盘空间来复制文件而旧文件仍然存在,则不起作用等)。
关于File.Move(源,目标)方法的MSDN文档没有指定它是否等待完成,但给出的代码示例进行了简单的File.Exists(…)检查,表示在那里拥有原始文件是“意外”的。
// Move the file.
File.Move(path, path2);
Console.WriteLine("{0} was moved to {1}.", path, path2);
// See if the original exists now.
if (File.Exists(path))
{
    Console.WriteLine("The original file still exists, which is unexpected.");
}
else
{
    Console.WriteLine("The original file no longer exists, which is expected.");
}   

    

也许你可以采用类似的方法,使用 while 循环检查新文件是否存在以及旧文件是否不存在,并在循环中设置“计时器”退出以防操作系统出现意外情况导致文件丢失。
// We perform the movement of the file
File.Move(source,destination);
// Sets an "exit" datetime, after wich the loop will end, for example 15 seconds. The moving process should always be quicker than that if files are in the same volume, almost immediate, but not if they are in different ones
DateTime exitDateTime = DateTime.Now.AddSeconds(15);
bool exitLoopByExpiration = false;
// We stops here until copy is finished (by checking fies existence) or the time limit excedes
while (File.Exists(source) && !File.Exists(destination) && !exitLoopByExpiration ) { 
// We compare current datetime with the exit one, to see if we reach the exit time. If so, we set the flag to exit the loop by expiration time, not file moving 
if (DateTime.Now.CompareTo(exitDateTime) > 0) { exitLoopByExpiration = true; }
}
//
if (exitLoopByExpiration) {
// We can perform extra work here, like log problems or throw exception, if the loop exists becouse of time expiration
}

我已经检查过这个解决方案,似乎可以正常工作。


1
请注意,只有当源文件和目标文件在同一个卷上时,File.Move 才能按您所说的方式工作。从文档中可以了解到:如果要将文件移动到不同的磁盘卷上,则相当于复制该文件并从源文件中删除它(如果复制成功)。 - Mike Christiansen
当然!指得好!;) - tomasofen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接