如何在创建目录时从目录中可靠地移动文件(异步)?

时间:2013-07-23 16:14:16

标签: c# file-io asynchronous

在我的Windows服务解决方案中,FileSystemWatcher监视新文件的目录树,每当它触发Created事件时,我都会尝试将文件异步移动到另一台服务器进行进一步处理。这是代码:

foreach (string fullFilePath in 
                Directory.EnumerateFiles(directoryToWatch, "*.*",  
                                         SearchOption.AllDirectories)
                         .Where(filename => fileTypes.Contains(Path.GetExtension(filename))))
            {
                string filename = Path.GetFileName(fullFilePath);
                using (FileStream sourceStream = File.Open(filename, FileMode.Open, FileAccess.Read))
                {
                    using (FileStream destStream = File.Create(Path.Combine(destination, filename)))
                    {
                        await sourceStream.CopyToAsync(destStream);
                    }
                }
            }

问题是,当这些文件被复制到我正在观看的文件夹中时,它们并不总是被解锁并且可供我使用。当我点击锁定的文件时,我想“重试”,但我不习惯异步思考,所以我不知道如何将错误的文件放回队列中。

1 个答案:

答案 0 :(得分:1)

首先,您需要'检测'异步执行过程中抛出的异常。这可以通过以下方式完成:

        try
        {
            await sourceStream.CopyToAsync(destStream);
        }
        catch (Exception copyException)
        {
        }

一旦检测到异常并进行了正确处理,即您确定某个特定异常是重试的原因,您将必须维护自己的重试目标(和目标)队列,这些队列将重试。

然后你必须组织一个新的入口点,这将导致自己重试。这样的入口点可以由您使用的文件系统监视器中的计时器或下一个事件触发(我不建议这样做)。对于多个故障的情况,您还必须对队列溢出执行检测。请记住,文件系统监视器中也存在此类溢出检测,如果系统事件太多(许多文件似乎一次复制到受监视的文件夹中),则可以跳过通知。

如果这些问题不会让您烦恼,我建议您实施一个计时器,或者更准确地说是超时,以便重试复制任务。 另一方面,如果您需要一个强大的解决方案,我将自己实现一个文件系统监视器。

关于超时,它可能如下所示:

    private Queue<myCopyTask> queue;
    private Timer retryTimeout;

    public Program()
    {
        retryTimeout = new Timer(QueueProcess, null, Timeout.Infinite, Timeout.Infinite);
    }

    private void FileSystemMonitorEventhandler()
    {
        //New tasks are provided by the file system monitor.
        myCopyTask newTask = new myCopyTask();
        newTask.sourcePath = "...";
        newTask.destinationPath = "...";

        //Keep in mind that queue is touched from different threads.
        lock (queue)
        {
            queue.Enqueue(newTask);
        }

        //Keep in mind that Timer is touched from different threads.
        lock (retryTimeout)
        {
            retryTimeout.Change(1000, Timeout.Infinite);
        }
    }

    //Start this routine only via Timer.
    private void QueueProcess(object iTimeoutState)
    {
        myCopyTask task = null;

        do
        {
            //Keep in mind that queue is touched from different threads.
            lock (queue)
            {
                if (queue.Count > 0)
                {
                    task = queue.Dequeue();
                }
            }

            if (task != null)
            {
                CopyTaskProcess(task);
            }
        } while (task != null);
    }

    private async void CopyTaskProcess(myCopyTask task)
    {
        FileStream sourceStream = null;
        FileStream destStream = null;

        try
        {
            sourceStream = File.OpenRead(task.sourcePath);
            destStream = File.OpenWrite(task.destinationPath);
            await sourceStream.CopyToAsync(destStream);
        }
        catch (Exception copyException)
        {
            task.retryCount++;

            //In order to avoid instant retries on several problematic tasks you probably 
            //should involve a mechanism to delay retries. Keep in mind that this approach
            //delays worker thread that is implicitly involved by await keyword.
            Thread.Sleep(100);

            //Keep in mind that queue is touched from different threads.
            lock (queue)
            {
                queue.Enqueue(task);
            }

            //Keep in mind that Timer is touched from different threads.
            lock (retryTimeout)
            {
                retryTimeout.Change(1000, Timeout.Infinite);
            }
        }
        finally
        {
            if (sourceStream != null)
            {
                sourceStream.Close();
            }

            if (destStream != null)
            {
                destStream.Close();
            }
        }
    }
}

internal class myCopyTask
{
    public string sourcePath;
    public string destinationPath;
    public long retryCount;
}
相关问题