c# - Web API 服务在读取流时挂起
问题描述
说明:我正在修改支持可恢复文件上传的 ASP.NET Core Web API 服务(托管在 Windows 服务中)。这可以正常工作并在许多失败情况下恢复文件上传,除了下面描述的一种情况。
问题:当服务在另一台计算机上并且客户端在我的计算机上并且我拔下计算机上的电缆时,客户端检测到网络缺失,而服务在 fileSection.FileStream.Read() 上挂起。有时服务会在 8 分钟内检测到故障,有时在 20 分钟内检测到故障,有时从不检测。
我还注意到,在拔掉电缆并停止客户端后,服务卡在 Read() 函数并且文件大小为 x KB,但是当服务最终检测到异常时,它会向文件中写入额外的 4 KB . 这很奇怪,因为我关闭了缓冲并且缓冲区大小为 2 KB。
问题:如何正确检测服务上没有网络,或者正确超时,或者取消请求
服务代码:
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;
}
我尝试了什么:
我将这些添加到配置文件中:
试图在服务器上设置超时
var host = new WebHostBuilder().UseKestrel(o => { o.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);})
使用异步和非异步 Read()
尝试保持活力和没有
尝试在网络恢复时中止请求:request?.Abort();
试图设置 formDataStream.ReadTimeout = 60000;
解决方案
由于我没有找到更好的方法,我决定在读取流中添加一个超时并将其保存到文件中。很好的例子贴在这里: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;
}
}
推荐阅读
- arrays - 为什么随机打乱的数组会重复
- arrays - 关于过滤/绘制numpy数组的问题
- git - 在 Cent 上安装 Gitlab
- javascript - 代码未按顺序执行(Firebase)。同步问题
- java - 在java中将一个列表转换为另一个列表
- r - R xts lag() 函数仅滞后 1 个位置
- c - 当我运行 Valgrind 时,我的代码中出现大小为 8 的无效写入错误
- html - 如何定位一个 div,在 100% 宽度的 body 中,左右空间相同?
- rust - PhantomData 在 Rust 中究竟是如何工作的?
- go - Golang 中的路由仅使用 stdlib