Solución:
Hay dos tiempos de espera que nos molestan al procesar la carga de un archivo grande. HttpWebRequest.Timeout
y HttpWebRequest.ReadWriteTimeout
. Necesitaremos abordar ambos.
HttpWebRequest.ReadWriteTimeout
Primero, abordemos HttpWebRequest.ReadWriteTimeout
. Necesitamos deshabilitar el “almacenamiento en búfer de secuencia de escritura”.
httpRequest.AllowWriteStreamBuffering = false;
Con esta configuración cambiada, su HttpWebRequest.ReadWriteTimeout
los valores funcionarán mágicamente como le gustaría, puede dejarlos en el valor predeterminado (5 minutos) o incluso disminuirlos. Utilizo 60 segundos.
Este problema surge porque, al cargar un archivo grande, si los datos están almacenados en búfer por .NET framework, su código pensará que la carga está terminada cuando no lo esté, y llamará HttpWebRequest.GetResponse()
demasiado pronto, que se agotará.
HttpWebRequest.Timeout
Ahora, abordemos HttpWebRequest.Timeout
.
Nuestro segundo problema surge porque HttpWebRequest.Timeout
se aplica a toda la carga. El proceso de carga en realidad consta de tres pasos (aquí hay un gran artículo que fue mi referencia principal):
- Una solicitud para comienzo la carga.
- Escritura de los bytes.
- Una solicitud para terminar la carga y obtenga cualquier respuesta del servidor.
Si tenemos un tiempo de espera que se aplica a toda la carga, necesitamos una gran cantidad para acomodar la carga de archivos grandes, pero también nos enfrentamos al problema de que un tiempo de espera legítimo tardará mucho en agotarse. Esta no es una buena situacion. En cambio, queremos un breve tiempo de espera (digamos 30 segundos) para aplicar a los pasos n. ° 1 y n. ° 3. No queremos un tiempo de espera general en el n. ° 2 en absoluto, pero queremos que la carga falle si los bytes dejan de escribirse durante un período de tiempo. Afortunadamente, ya hemos abordado el n. ° 2 con HttpWebRequest.ReadWriteTimeout
, solo tenemos que corregir el comportamiento molesto de HttpWebRequest.Timeout
. Resulta que las versiones asincrónicas de GetRequestStream
y GetResponse
hacer exactamente lo que necesitamos.
Entonces quieres este código:
public static class AsyncExtensions
{
public static Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
{
return Task.Factory.StartNew(() =>
{
var b = task.Wait((int)timeout.TotalMilliseconds);
if (b) return task.Result;
throw new WebException("The operation has timed out", WebExceptionStatus.Timeout);
});
}
}
Y en lugar de llamar GetRequestStream
y GetResponse
llamaremos a las versiones asincrónicas:
var uploadStream = httpRequest.GetRequestStreamAsync().WithTimeout(TimeSpan.FromSeconds(30)).Result;
Y de manera similar para la respuesta:
var response = (HttpWebResponse)httpRequest.GetResponseAsync().WithTimeout(TimeSpan.FromSeconds(30)).Result;
Eso es todo lo que necesita. Ahora tus cargas serán mucho más confiables. Puede envolver toda la carga en un ciclo de reintento para mayor certeza.