Search code examples
c#httptimeout

HttpClient timeout - data response vs request completion


I have an application that has to download a large payload (a few hundred MB), and in some circumstances on a device with a slow connection.

So I have this:

using (var client = new HttpClient())
{
    client.BaseAddress = new Uri(activeResource.Content);
    client.Timeout = TimeSpan.FromHours(6);
    binary = await client.GetByteArrayAsync(activeResource.Content);
}

I've set the Timeout to be 6 hours to be safe - in realistic terms we have seem instances where it has taken up to an hour to download.

My question is: Does this Timeout specify the timeout for the entire transaction, or is it the timeout while waiting for a response?

In other words, what I want is, to allow for up to 6 hours (or thereabouts) for the entire download to complete, but if, say there is no data coming through, due to, say a dropped connection, I don't want the user to have to wait 6 hours to find out.

And if it's the former, how do I set a short timeout for waiting for a response from the server, but allow a long time for the download to complete?


Solution

  • What ended up doing the trick was a variation of Jeevan Abi's answer

    I ditched the TimeOut property altogether and put the read into a cancellable task.

    {
        var downloadTimeout = TimeSpan.FromHours(6);
        var inactivityTimeout = TimeSpan.FromSeconds(30);
    
        using (var client = new HttpClient())
        {
            client.Timeout = Timeout.InfiniteTimeSpan;
    
            var cancellationTokenSource = new CancellationTokenSource(downloadTimeout);
            using (var response = await client.GetAsync(resource.ResourceUrl, HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token))
            {
                response.EnsureSuccessStatusCode();
                
                using (var contentStream = await response.Content.ReadAsStreamAsync())
                {
                    using (var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
                    {
                        var buffer = new byte[8192];
                        int bytesRead;
                        long totalBytesRead = 0;
    
                        while ((bytesRead = await ReadDataAsync(contentStream, buffer, 0, buffer.Length, inactivityTimeout, cancellationTokenSource.Token)) > 0)
                        {
                            await fileStream.WriteAsync(buffer, 0, bytesRead);
                            totalBytesRead += bytesRead;
                        }
                    }
                }
    
            }
        }
    }
    
    
    private async Task<int> ReadDataAsync(Stream stream, byte[] buffer, int offset, int count, TimeSpan timeout, CancellationToken cancelToken)
    {
        var readTask = Task.Run(() => stream.ReadAsync(buffer, offset, count), cancelToken);
    
        if (await Task.WhenAny(readTask, Task.Delay(timeout)) == readTask)
        {
            return await readTask;
        }
        else
        {
            throw new TimeoutException("Connection timed out during download.");
        }
    }