如何在使用File.Copy时实现超时?

本文关键字:实现 超时 Copy File | 更新日期: 2023-09-27 17:53:44

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

如何在使用File.Copy时实现超时?

例如,可以使用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操作。

您可以实现如下简单方法,构建在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(),它会在指定的时间后自动取消自身。

你可以使用async:

复制方法
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