You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 08:10:53