【问题标题】:How do I implement a timeout when using File.Copy?使用 File.Copy 时如何实现超时?
【发布时间】:2015-08-21 07:27:06
【问题描述】:

我正在使用 System.IO.File.Copy 将文件从远程共享复制到我的本地系统。如果复制时间过长,如何实现超时?

【问题讨论】:

标签: c# .net file-io io timeout


【解决方案1】:

例如,可以这样使用async-await模式:

Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(10));

// I use a completion source to set File.Copy thread from its own
// thread, and use it later to abort it if needed
TaskCompletionSource<Thread> copyThreadCompletionSource = new TaskCompletionSource<Thread>();

// This will await while any of both given tasks end.
await Task.WhenAny
(
    timeoutTask,
    Task.Factory.StartNew
    (
        () =>
        {
            // This will let main thread access this thread and force a Thread.Abort
            // if the operation must be canceled due to a timeout
            copyThreadCompletionSource.SetResult(Thread.CurrentThread);
            File.Copy(@"C:\x.txt", @"C:\y.txt");
        }
    )
);


// Since timeoutTask was completed before wrapped File.Copy task you can 
// consider that the operation timed out
if (timeoutTask.Status == TaskStatus.RanToCompletion)
{
    // Timed out!
    Thread copyThread = await copyThreadCompletionSource.Task;
    copyThread.Abort();
}

您可以封装它以便在需要时重新使用它:

public static class Timeout
{
    public static async Task<bool> ForAsync(Action operationWithTimeout, TimeSpan maxTime)
    {
        Contract.Requires(operationWithTimeout != null);

        Task timeoutTask = Task.Delay(maxTime);
        TaskCompletionSource<Thread> copyThreadCompletionSource = new TaskCompletionSource<Thread>();

        // This will await while any of both given tasks end.
        await Task.WhenAny
        (
            timeoutTask,
            Task.Factory.StartNew
            (
                () =>
                {
                    // This will let main thread access this thread and force a Thread.Abort
                    // if the operation must be canceled due to a timeout
                    copyThreadCompletionSource.SetResult(Thread.CurrentThread);
                    operationWithTimeout();
                }
            )
        );


        // Since timeoutTask was completed before wrapped File.Copy task you can 
        // consider that the operation timed out
        if (timeoutTask.Status == TaskStatus.RanToCompletion)
        {
            // Timed out!
            Thread copyThread = await copyThreadCompletionSource.Task;
            copyThread.Abort();

            return false;
        }
        else
        {
            return true;
        }             
    }
}

在您的项目中,您可能会这样调用上述方法:

bool success = await Timeout.ForAsync(() => File.Copy(...), TimeSpan.FromSeconds(10));

if(success)
{
   // Do stuff if File.Copy didn't time out!
}

注意我使用了Thread.Abort() 而不是CancellationToken。在您的用例中,您需要调用不能使用所谓的取消模式的同步方法,我相信这可能是Thread.Abort() 可能是有效选项的少数情况之一

一天结束时,如果超时,代码将中止执行File.Copy 的线程,因此,应该足以停止 I/O 操作。

【讨论】:

  • 感谢您的回答。现在我有一个后续问题:如果在 File.Copy 完成之前发生超时,我可以取消 File.Copy 任务吗?如果可以,如何?
  • @JeffR 我会在今天或明天回答您...顺便说一句,您可以自己加快回答速度,查看以下文章,您应该能够修改我的示例以满足您的需求: msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx
  • 谢谢。我看到了,但我不明白如何中断 File.Copy - File.Copy 如何在请求时抛出异常?
  • @JeffR 您根本无法中断 File.Copy... 今天晚上我会在答案更新中给您一些提示...
  • @JeffR 我已经更新了我的答案。更新后的方法应该适用于您的场景......让我知道它是否适合您:)
【解决方案2】:

您可以实现一个类似以下的简单方法,该方法基于接受取消令牌的Stream.CopyToAsync()

static async Task Copy(string destFilePath, string sourceFilePath, int timeoutSecs)
{
    var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSecs));

    using (var dest = File.Create(destFilePath))
    using (var src = File.OpenRead(sourceFilePath))
    {
        await src.CopyToAsync(dest, 81920, cancellationSource.Token);
    }
}

如您所见,可以创建一个CancellationTokenSource(),它会在指定时间后自动取消。

您可以通过异步使用 Copy 方法:

try
{
    await Copy(@"c:\temp\test2.bin", @"c:\temp\test.bin", 60);
    Console.WriteLine("finished..");
}
catch (OperationCanceledException ex)
{
    Console.WriteLine("cancelled..");
}
catch (Exception ex)
{
    Console.WriteLine("error..");
}

还是老办法:

var copyInProgress = Copy(@"c:\temp\test2.bin", @"c:\temp\test.bin", 60);

copyInProgress.ContinueWith(
        _ => { Console.WriteLine("cancelled.."); },
        TaskContinuationOptions.OnlyOnCanceled
    );

copyInProgress.ContinueWith(
        _ => { Console.WriteLine("finished.."); },
        TaskContinuationOptions.OnlyOnRanToCompletion
    );

copyInProgress.ContinueWith(
        _ => { Console.WriteLine("failed.."); },
        TaskContinuationOptions.OnlyOnFaulted
    );

copyInProgress.Wait();

使用可由用户控制的第二个取消令牌很容易改进上述代码(通过取消按钮)。你只需要使用CancellationTokenSource.CreateLinkedTokenSource

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-12
    • 1970-01-01
    • 2012-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多