更新
我暂时把这件事放在一边,但现在又回来了。我找不到任何有帮助的东西。有谁知道我可以调试 CopyToAsync 函数内部发生的情况的方法,或者我可以查看瓶颈所在的方法。从客户端机器端或网络服务器端。我担任开发人员已有一段时间了,但对 Web 开发还是新手,所以我不熟悉浏览器开发工具或任何可以帮助我找到此问题的工具。任何建议将不胜感激,因为我现在完全陷入困境。
问题
我的上传速度似乎非常慢。上传 8MB 文件大约需要 22 或 23 秒。在 LAN 内,网络服务器启动需要 2 或 3 秒。通过一些日志记录,我缩小了范围,所有时间都花在了对 Stream.CopyToAsync() 的调用上。在底部我放置了两个日志输出来显示本地和远程之间的上传差异。 CopyToAsync 之前和之后的所有其他代码都运行得很快。我已经包含了下面制作的 blazor 组件的代码。
研究
我已经检查了以下案例以及许多其他阅读材料,但似乎还没有找到任何有帮助的内容。
我应该在每个等待的操作上调用ConfigureAwait(false)吗
提高流式传输大型 (1-10 GB) 文件的速度 .Net Core
将文件上传到部署为 Azure Web 应用程序的 Blazor 服务器应用程序时速度极慢
我尝试过的
正如您在代码中看到的,我尝试向 FileStream 添加一些 FileOptions,但这对速度没有影响。我也尝试过ConfigureAwait,但这并没有提高速度,而且在第一个文件后也崩溃了。我认为这是因为这是在 UI 线程中。至少这是我从研究中得出的假设。我对编程并不陌生,但对 Web 编程尤其是 Blazor 很陌生。
其他要点
服务器和远程客户端都位于具有高速互联网的位置(同一城市内),并且网络服务器没有沉重的负载。
问题
8MB 23 秒对于代码的设计方式来说是正常的还是你认为这也太多了?
我的代码是正确的方法吗?除了速度之外,它的功能确实相当稳定。
对于尝试或研究解决此问题有什么建议吗?
代码
@using System.IO
@inject SessionService session
@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment env
@inject IJSRuntime JSRuntime
@if (showMessage)
{
<div style="font-size: 20px;" class="alert-danger border-danger">
<p>@userMessage</p>
</div>
}
<button class="btn btn-primary m-2" onclick="document.getElementById('filepicker').click()" disabled="@disabled">@buttonText</button>
<InputFile id="filepicker" OnChange="@OnInputFileChange" hidden multiple="@multipleFiles" />
@code
{
[Parameter] public EventCallback<FileModel> OnFileAdd { get; set; }
[Parameter] public EventCallback<int> OnUploadStart { get; set; }
[Parameter] public EventCallback<bool> OnUploadEnd { get; set; }
[Parameter] public string buttonText { get; set; } = "Upload";
[Parameter] public bool multipleFiles { get; set; } = false;
[Parameter] public int callBackRefID { get; set; } = 0;
private IReadOnlyList<IBrowserFile> selectedFiles;
private bool showMessage = false;
private bool disabled = false;
private MarkupString userMessage;
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
if (e.FileCount > 0)
{
disabled = true;
await JSRuntime.InvokeAsync<string>("console.log", "Starting " + e.FileCount + " files - " + DateTime.Now.ToString());
await OnUploadStart.InvokeAsync(e.FileCount);
selectedFiles = e.GetMultipleFiles(e.FileCount);
foreach (var file in selectedFiles)
{
await JSRuntime.InvokeAsync<string>("console.log", "Starting " + file.Name + " - " + DateTime.Now.ToString());
Stream stream = file.OpenReadStream((long)2147483648);
var path = $"{env.WebRootPath}\\Uploads\\{file.Name}";
//FileOptions had no effect on performance
FileStream fs = File.Create(path, 1048576, FileOptions.Asynchronous | FileOptions.SequentialScan);
//FileStream fs = File.Create(path, 1048576);
await JSRuntime.InvokeAsync<string>("console.log", "Before stream.CopyToAsync - " + DateTime.Now.ToString());
await stream.CopyToAsync(fs, 1048576);
//ConfigureAwait causes crash after first file is done.
//await stream.CopyToAsync(fs, 1048576).ConfigureAwait(false);
await JSRuntime.InvokeAsync<string>("console.log", "After stream.CopyToAsync - " + DateTime.Now.ToString());
stream.Close();
fs.Close();
FileModel upFile = new();
upFile.S_ID = session.CurrentUser.ID;
upFile.Name = file.Name;
upFile.DisplayName = "";
upFile.Descr = "";
upFile.ContentType = file.ContentType;
if (upFile.GetFileData(path, file.ContentType) == false)
{
userMessage = new(upFile.ErrMsg);
showMessage = true;
await OnUploadEnd.InvokeAsync(false);
return;
}
await OnFileAdd.InvokeAsync(upFile);
await JSRuntime.InvokeAsync<string>("console.log", "Finished " + file.Name + " - " + DateTime.Now.ToString());
}
await OnUploadEnd.InvokeAsync(true);
await JSRuntime.InvokeAsync<string>("console.log", "Finished - " + DateTime.Now.ToString());
disabled = false;
}
}
}
本地日志
blazor.server.js:1 Starting 3 files - 2021-04-12 2:01:33 PM
blazor.server.js:1 Starting IMG_7830.JPG - 2021-04-12 2:01:33 PM
blazor.server.js:1 Before stream.CopyToAsync - 2021-04-12 2:01:33 PM
blazor.server.js:1 After stream.CopyToAsync - 2021-04-12 2:01:36 PM
blazor.server.js:1 Finished IMG_7830.JPG - 2021-04-12 2:01:37 PM
blazor.server.js:1 Starting IMG_7831.JPG - 2021-04-12 2:01:37 PM
blazor.server.js:1 Before stream.CopyToAsync - 2021-04-12 2:01:37 PM
blazor.server.js:1 After stream.CopyToAsync - 2021-04-12 2:01:40 PM
blazor.server.js:1 Finished IMG_7831.JPG - 2021-04-12 2:01:41 PM
blazor.server.js:1 Starting IMG_7832.JPG - 2021-04-12 2:01:41 PM
blazor.server.js:1 Before stream.CopyToAsync - 2021-04-12 2:01:41 PM
blazor.server.js:1 After stream.CopyToAsync - 2021-04-12 2:01:43 PM
blazor.server.js:1 Finished IMG_7832.JPG - 2021-04-12 2:01:44 PM
blazor.server.js:1 Finished - 2021-04-12 2:01:44 PM
远程日志
blazor.server.js:1 Starting 3 files - 2021-04-12 12:01:44 PM
blazor.server.js:1 Starting IMG_7830.JPG - 2021-04-12 12:01:45 PM
blazor.server.js:1 Before stream.CopyToAsync - 2021-04-12 12:01:45 PM
blazor.server.js:1 After stream.CopyToAsync - 2021-04-12 12:02:07 PM
blazor.server.js:1 Finished IMG_7830.JPG - 2021-04-12 12:02:08 PM
blazor.server.js:1 Starting IMG_7831.JPG - 2021-04-12 12:02:08 PM
blazor.server.js:1 Before stream.CopyToAsync - 2021-04-12 12:02:08 PM
blazor.server.js:1 After stream.CopyToAsync - 2021-04-12 12:02:30 PM
blazor.server.js:1 Finished IMG_7831.JPG - 2021-04-12 12:02:31 PM
blazor.server.js:1 Starting IMG_7832.JPG - 2021-04-12 12:02:31 PM
blazor.server.js:1 Before stream.CopyToAsync - 2021-04-12 12:02:31 PM
blazor.server.js:1 After stream.CopyToAsync - 2021-04-12 12:02:53 PM
blazor.server.js:1 Finished IMG_7832.JPG - 2021-04-12 12:02:54 PM
blazor.server.js:1 Finished - 2021-04-12 12:02:54 PM
对我来说,将 8MB 文件上传到我的服务器只需不到一秒。我定期将 50MB 的库存照片图像上传到我的网站 - 包括处理和图像大小调整,每次平均需要几秒钟。
所以,正如您怀疑的那样,这里有问题。
乍一看,我所做的唯一明显不同的是我没有为流副本指定缓冲区长度。不过,我想您已经玩过所有这些东西了。
你说的是同城服务器,但是什么样的服务器呢?我假设您没有使用共享主机或类似的东西,对吧?在注册 Microsoft VM 之前,我使用 Godaddy 大约 2 天。
我会使用
Parallel.ForEach
并避免异步。同步代码通常运行得更快,这是一项非常适合并行性的任务。
此外,您的
using
上缺少 OpenReadStream
。当您拥有 using 块时,您不需要手动关闭或刷新流。
一旦处于并行环境中,
userMessage =
和showMessage = true;
就变得毫无意义。不要访问并行上下文之外的共享状态。
Parallel.ForEach(selectedFiles, file =>
{
var path = $"{env.WebRootPath}\\Uploads\\{file.Name}";
const int kb = 1024;
const int mb = 1024 * kb;
using FileStream fs = new(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, mb, FileOptions.Asynchronous);
using Stream stream = file.OpenReadStream(2147483648);
stream.CopyTo(fs, mb);
FileModel upFile = new();
upFile.S_ID = session.CurrentUser.ID;
upFile.Name = file.Name;
upFile.DisplayName = "";
upFile.Descr = "";
upFile.ContentType = file.ContentType;
if (upFile.GetFileData(path, file.ContentType) == false)
{
userMessage = new(upFile.ErrMsg);
showMessage = true;
OnUploadEnd.Invoke(false);
return;
}
OnFileAdd.Invoke(upFile);
}