1

在我的 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);
                    }
                }
            }

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

4

1 回答 1

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;
}
于 2013-07-23T17:24:42.827 回答