UWP应用中基于HttpClient实现文件上传进度追踪的技术咨询
UWP应用中基于HttpClient实现文件上传进度追踪的技术咨询
嘿,我之前在UWP里做文件上传进度追踪的时候也踩过不少坑,试过自定义ProgressStreamContent没生效的情况也遇到过,给你分享几个亲测有效的方案,应该能帮你解决问题!
先说说为什么你之前的自定义ProgressStreamContent可能没生效
大概率是在流的序列化逻辑或者进度回调的线程处理上出了问题——比如没正确重写SerializeToStreamAsync方法,或者进度更新没切回UI线程导致看不到变化。先给你一个正确的自定义ProgressStreamContent实现:
public class ProgressStreamContent : HttpContent { private readonly Stream _sourceStream; private readonly int _bufferSize; private readonly IProgress<long> _progressReporter; private long _totalBytesUploaded; public ProgressStreamContent(Stream sourceStream, int bufferSize = 4096, IProgress<long> progressReporter = null) { _sourceStream = sourceStream ?? throw new ArgumentNullException(nameof(sourceStream)); _bufferSize = bufferSize > 0 ? bufferSize : 4096; _progressReporter = progressReporter ?? throw new ArgumentNullException(nameof(progressReporter)); // 复制源文件的Content-Type和Content-Length头 if (_sourceStream.CanSeek) { Headers.ContentLength = _sourceStream.Length; } Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); } protected override async Task SerializeToStreamAsync(Stream targetStream, TransportContext context) { var buffer = new byte[_bufferSize]; _totalBytesUploaded = 0; int bytesRead; while ((bytesRead = await _sourceStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await targetStream.WriteAsync(buffer, 0, bytesRead); _totalBytesUploaded += bytesRead; _progressReporter.Report(_totalBytesUploaded); } } protected override bool TryComputeLength(out long length) { length = _sourceStream.CanSeek ? _sourceStream.Length : -1; return _sourceStream.CanSeek; } protected override void Dispose(bool disposing) { if (disposing) { _sourceStream.Dispose(); } base.Dispose(disposing); } }
改造你的API上传方法
把原来无进度的上传方法改成带进度追踪的版本,这里我们会把已上传字节和总字节一起返回给UI层:
public async Task<bool> UploadFileWithProgressAsync(string filePath, string apiEndpoint, IProgress<(long UploadedBytes, long TotalBytes)> progress) { var fileInfo = new FileInfo(filePath); long totalFileBytes = fileInfo.Length; using (var httpClient = new HttpClient()) using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { // 包装进度回调,把已上传字节和总字节一起传递 var progressHandler = new Progress<long>(uploaded => { progress.Report((uploaded, totalFileBytes)); }); using (var progressContent = new ProgressStreamContent(fileStream, 4096, progressHandler)) { var response = await httpClient.PostAsync(apiEndpoint, progressContent); return response.IsSuccessStatusCode; } } }
UI层调用注意事项
关键!进度回调是在后台线程触发的,UWP的UI元素只能在UI线程更新,所以必须用Dispatcher切回去:
private async void UploadBtn_Click(object sender, RoutedEventArgs e) { // 先通过FileOpenPicker获取要上传的文件路径 var picker = new FileOpenPicker(); picker.FileTypeFilter.Add("*"); var selectedFile = await picker.PickSingleFileAsync(); if (selectedFile == null) return; var apiUrl = "https://your-server.com/api/upload"; var progressTracker = new Progress<(long UploadedBytes, long TotalBytes)>(status => { _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { double progressPercent = (double)status.UploadedBytes / status.TotalBytes * 100; UploadProgressBar.Value = progressPercent; ProgressTxtBlock.Text = $"已上传: {status.UploadedBytes / 1024 / 1024:F2} MB / {status.TotalBytes / 1024 / 1024:F2} MB"; }); }); bool uploadSuccess = await YourApiService.UploadFileWithProgressAsync(selectedFile.Path, apiUrl, progressTracker); if (uploadSuccess) { await new MessageDialog("上传成功!").ShowAsync(); } else { await new MessageDialog("上传失败,请重试!").ShowAsync(); } }
更省心的替代方案:用UWP原生的Windows.Web.Http.HttpClient
如果你不想自己写自定义Content,UWP自带的Windows.Web.Http.HttpClient支持原生的进度追踪,不用额外造轮子:
public async Task<bool> UploadWithNativeProgressAsync(StorageFile file, string apiEndpoint, IProgress<HttpProgress> progress) { var fileProps = await file.GetBasicPropertiesAsync(); long totalBytes = fileProps.Size; using (var client = new Windows.Web.Http.HttpClient()) using (var fileStream = await file.OpenStreamForReadAsync()) { var content = new Windows.Web.Http.HttpStreamContent(fileStream.AsInputStream()); content.Headers.ContentType = new Windows.Web.Http.Headers.HttpMediaTypeHeaderValue("application/octet-stream"); var request = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.Post, new Uri(apiEndpoint)); request.Content = content; // 直接传入Progress对象,框架会自动追踪上传进度 var uploadTask = client.SendRequestAsync(request).AsTask(progress); var response = await uploadTask; return response.IsSuccessStatusCode; } }
调用的时候也类似,只是进度参数变成HttpProgress,里面包含了BytesSent和TotalBytesToSend等字段:
private async void NativeUploadBtn_Click(object sender, RoutedEventArgs e) { var picker = new FileOpenPicker(); picker.FileTypeFilter.Add("*"); var selectedFile = await picker.PickSingleFileAsync(); if (selectedFile == null) return; var apiUrl = "https://your-server.com/api/upload"; var progressTracker = new Progress<HttpProgress>(status => { _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { if (status.TotalBytesToSend.HasValue) { double progressPercent = (double)status.BytesSent / status.TotalBytesToSend.Value * 100; UploadProgressBar.Value = progressPercent; ProgressTxtBlock.Text = $"已上传: {status.BytesSent / 1024 / 1024:F2} MB / {status.TotalBytesToSend.Value / 1024 / 1024:F2} MB"; } }); }); bool success = await YourApiService.UploadWithNativeProgressAsync(selectedFile, apiUrl, progressTracker); // 处理结果提示 }
几个关键注意点
- 缓冲区大小别设太小,4096或8192字节就够了,太小会频繁触发进度回调,拖慢上传速度。
- 一定要用
using语句包裹流和HttpClient,确保资源正确释放。 - 如果服务器返回的是分块响应(没有Content-Length),进度计算会不准确,这种情况需要和后端配合返回正确的Content-Length。
如果还是有问题,可以把你之前写的自定义ProgressStreamContent代码贴出来,或者说说API的具体返回情况,我再帮你排查!
备注:内容来源于stack exchange,提问作者lila
相关产品推荐
相关产品推荐

