我正在尝试使用 multipartform-data 通过我的 API 上传 *.iso 文件并将它们流式传输到本地文件夹中。 我使用 Stream.CopyAsync(destinationStream) ,它运行缓慢,但还不错。但现在我需要报告进展情况。因此,我使用了自定义 CopyTOAsync 并向其添加了进度报告。但该方法非常慢(根本无法接受),即使与 Stream::CopyToASync 相比也是如此。
public async Task CopyToAsync(Stream source, Stream destination, long? contentLength, ICommandContext context, int bufferSize = 81920 )
{
var buffer = new byte[bufferSize];
int bytesRead;
long totalRead = 0;
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await destination.WriteAsync(buffer, 0, bytesRead);
totalRead += bytesRead;
context.Report(CommandResources.RID_IMAGE_LOADIND, Math.Clamp((uint)((totalRead * 100) / contentLength), 3, 99));
}
_logger.Info($"Total read during upload : {totalRead}");
}
我尝试过的: Stream::CopyToAsync 的默认缓冲区大小是 81920 字节,我首先使用相同的值,然后尝试将缓冲区大小增加到 104857600 字节 - 没有区别。
关于如何提高自定义 CopyToAsync 的性能,您还有其他想法吗?
最佳答案
- 始终使用
ConfigureAwait
与await
为异步延续指定线程同步。- 根据平台不同,省略
ConfigureAwait
可能默认与 UI 线程(WPF、WinForms)或任何线程(ASP.NET Core)同步。如果它与 Stream 复制操作中的 UI 线程同步,那么性能急剧下降也就不足为奇了。 - 如果您在线程同步上下文中运行代码,那么您的
await
语句将被不必要地延迟,因为程序将继续安排到可能很忙的线程。
- 根据平台不同,省略
- 使用大小至少为几百 KiB 的缓冲区 - 甚至兆字节大小的缓冲区来进行异步操作 - 而不是典型的 4KiB 或 80KiB 大小的数组。
- 如果您使用
FileStream
确保您使用了FileOptions.Asynchronous
或useAsync: true
否则FileStream
将通过使用线程池线程而不是 Windows 的 native 异步 IO 执行阻塞 IO 来伪造其异步操作。
关于您的实际代码 - 只需使用 Stream::CopyToAsync
而不是自己重新实现。如果您想要进度报告,请考虑子类化 Stream
(作为代理包装器)。
这是我编写代码的方式:
- First, add my
ProxyStream
class from this GitHub Gist到您的项目。 - 然后子类
ProxyStream
添加对IProgress
的支持: - 确保任何
FileStream
实例是使用FileOptions.Asynchronous | FileOptions.SequentialScan
创建的. - 使用
CopyToAsync
.
public class ProgressProxyStream : ProxyStream
{
private readonly IProgress<(Int64 soFar, Int64? total)> progress;
private readonly Int64? total;
public ProgressProxyStream( Stream stream, IProgress<Int64> progress, Boolean leaveOpen )
: base( stream, leaveOpen )
{
this.progress = progress ?? throw new ArgumentNullException(nameof(progress));
this.total = stream.CanSeek ? stream.Length : (Int64?)null;
}
public override Task<Int32> ReadAsync( Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken )
{
this.progress.Report( ( offset, this.total ) );
return this.Stream.ReadAsync( buffer, offset, count, cancellationToken );
}
}
如果性能仍然受到上述影响 ProgressProxyStream
那么我愿意打赌瓶颈就在 IProgress.Report
内部回调目标(我假设它与 UI 线程同步)- in which case a better solution is to use a ( System.Threading.Channels.Channel
)对于ProgressProxyStream
(或者甚至是 IProgress<T>
的实现)转储进度报告,而不阻塞任何其他 IO 事件。
关于c# - 提高流式传输大型 (1-10 GB) 文件的速度 .Net Core,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60772727/