Сервис Web API зависает при чтении потока - PullRequest
0 голосов
/ 16 мая 2018

Описание : я изменяю службу ASP.NET Core Web API (размещенную в службе Windows), которая поддерживает возобновляемую загрузку файлов. Это работает нормально и возобновляет загрузку файлов при многих условиях отказа, за исключением одного, описанного ниже.

Проблема : Когда служба находится на другом компьютере, а клиент на моем, и я отсоединяю кабель на своем компьютере, клиент обнаруживает отсутствие сети, пока служба зависает на fileSection.FileStream. Читать(). Иногда служба обнаруживает сбой за 8 минут, иногда за 20, иногда никогда.

Я также заметил, что после того, как я отсоединяю кабель и останавливаю клиента, служба застревает в функции Read () и размер файла составляет x КБ, но когда служба наконец обнаруживает исключение через некоторое время, она записывает дополнительные 4 КБ в файл. Это странно, потому что я отключил буферизацию и размер буфера составляет 2 КБ.

Вопрос : Как правильно определить отсутствие сети в службе, или правильно отключить время ожидания, или отменить запрос

Сервисный код :

public static async Task<List<(Guid, string)>> StreamFileAsync(
   this HttpRequest request, DeviceId deviceId, FileTransferInfo transferInfo)
    {
        var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), DefaultFormOptions.MultipartBoundaryLengthLimit);
        var reader = new MultipartReader(boundary, request.Body);
        var section = await reader.ReadNextSectionAsync(_cancellationToken);

        if (section != null)
        {
            var fileSection = section.AsFileSection();
            var targetPath = transferInfo.FileTempPath;

            try
            {
                using (var outfile = new FileStream(transferInfo.FileTempPath, FileMode.Append, FileAccess.Write, FileShare.None))
                {
                    var buffer = new byte[DefaultCopyBufferSize];
                    int read;

                    while ((read = fileSection.FileStream.Read(buffer, 0, buffer.Length)) > 0) // HANGS HERE
                    {
                        outfile.Write(buffer, 0, read);
                        transferInfo.BytesSaved = read + transferInfo.BytesSaved;
                    }
                }
            }
            catch (Exception e)
            {
                ...
            }
        }
    }

Код клиента :

var request = CreateRequest(fileTransferId, boundary, header, footer, filePath, offset, headers, null);

using (Stream formDataStream = request.GetRequestStream())
    {
            formDataStream.ReadTimeout = 60000;

            formDataStream.Write(Encoding.UTF8.GetBytes(header), 0, header.Length);
            byte[] buffer = new byte[2048];

            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                fs.Seek(offset, SeekOrigin.Begin);

                for (int i = 0; i < fs.Length - offset;)
                {
                    int k = await fs.ReadAsync(buffer, 0, buffer.Length);
                    if (k > 0)
                    {
                        await Task.Delay(100);
                        await formDataStream.WriteAsync(buffer, 0, k);
                    }

                    i = i + k;
                }
            }

            formDataStream.Write(footer, 0, footer.Length);
        }

        var uploadingResult = request.GetResponse() as HttpWebResponse;



private static HttpWebRequest CreateRequest(
        Guid fileTransferId,
        string boundary,
        string header,
        byte[] footer,
        string filePath,
        long offset,
        NameValueCollection headers,
        Dictionary<string, string> postParameters)
    {
        var url = $"{_BaseAddress}v1/ResumableUpload?fileTransferId={fileTransferId}";
        HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
        request.Method = "POST";
        request.ContentType = "multipart/form-data; boundary=\"" + boundary + "\"";
        request.UserAgent = "Agent 1.0";
        request.Headers.Add(headers); // custom headers
        request.Timeout = 120000;
        request.KeepAlive = true;
        request.AllowReadStreamBuffering = false;
        request.ReadWriteTimeout = 120000;
        request.AllowWriteStreamBuffering = false;
        request.ContentLength = CalculateContentLength(filePath, offset, header, footer, postParameters, boundary);
        return request;
    }

Что я пробовал :

  1. Я добавил их в конфигурационные файлы:

  2. Попытка установить тайм-аут на сервере

    var host = new WebHostBuilder (). UseKestrel (o => {o.Limits.KeepAliveTimeout = TimeSpan.FromMinutes (2);})

  3. Используется асинхронное и не асинхронное чтение ()

  4. Пробовал с сохранением в живых и без

  5. Попытка прервать запрос при восстановлении сети: request? .Abort ();

  6. Попытка установить formDataStream.ReadTimeout = 60000;

1 Ответ

0 голосов
/ 19 мая 2018

Так как я не нашел лучшего способа, я решил добавить таймаут в поток чтения и сохранить его в файл. Хороший пример был размещен здесь: https://blogs.msdn.microsoft.com/pfxteam/2012/10/05/how-do-i-cancel-non-cancelable-async-operations/

public static async Task<List<(Guid, string)>> StreamFileAsync(this HttpRequest request, DeviceId deviceId, FileTransferInfo transferInfo)
{
    var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), DefaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, request.Body);
    var section = await reader.ReadNextSectionAsync(_cancellationToken);

    if (section != null)
    {
        var fileSection = section.AsFileSection();
        var targetPath = transferInfo.FileTempPath;

        try
        {
            await SaveMyFile(...);
        }
        catch (OperationCanceledException){...}
        catch (Exception){...}
    }
}

private static async Task SaveMyFile(...)
{
        var cts = CancellationTokenSource.CreateLinkedTokenSource(myOtherCancellationToken);
        cts.CancelAfter(streamReadTimeoutInMs);
        var myReadTask = StreamFile(transferInfo, fileSection, cts.Token);
        await ExecuteMyTaskWithCancellation(myReadTask, cts.Token);
}


private static async Task<T> ExecuteMyTaskWithCancellation<T>(Task<T> task, CancellationToken cancellationToken)
{
        var tcs = new TaskCompletionSource<bool>();

        using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetResult(true), tcs))
        {
            if (task != await Task.WhenAny(task, tcs.Task))
            {
                throw new OperationCanceledException(cancellationToken);
            }
        }

        return await task;
}

private static async Task<bool> StreamFile(...)
{
        using (var outfile = new FileStream(transferInfo.FileTempPath, FileMode.Append, FileAccess.Write, FileShare.None))
        {
            var buffer = new byte[DefaultCopyBufferSize];
            int read;

            while ((read = await fileSection.FileStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
            {
                if (token.IsCancellationRequested)
                {
                    break;
                }

                await outfile.WriteAsync(buffer, 0, read);
                transferInfo.BytesSaved = read + transferInfo.BytesSaved;
            }

            return true;
        }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...