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

MAUI上传Azure Blob存储时取消操作的UI响应与异常问题

MAUI Azure Blob上传:解决取消时UI无响应、异常刷屏及并发控制问题

问题背景

开发MAUI应用实现文件上传至Azure Blob Storage,通过切换开关控制上传启停,但在取消操作时遇到UI响应性与异常处理问题,同时对并发控制的实现存在疑问。

当前实现

  • 切换开关绑定上传启停逻辑
  • 通过CancellationTokenSource实现上传取消
  • 用SemaphoreSlim控制并发上传,支持取消进行中及待处理的上传任务

遇到的问题

  • 取消上传时UI卡顿无响应
  • 控制台被TaskCanceledException大量刷屏
  • 需要区分处理已启动的上传(触发OperationCanceledException)与待启动的上传(需通过CancelPendingUploadsAsync处理)
  • 疑似过度使用Task.Run:StartSyncAsync已在独立线程执行,但仍在UploadFilesAsync中用Task.Run启动每个上传任务
  • 对Azure API的TransferOptions.MaximumConcurrency理解有误,设置该属性后100个上传任务仍同时启动,不得不依赖SemaphoreSlim控制并发

解决方案

1. 修复UI无响应问题

取消操作导致UI卡顿,通常是因为取消逻辑中存在阻塞UI线程的同步代码:

  • 确保所有取消相关操作(如CancellationTokenSource.Cancel()、清理待处理任务)都在异步方法中执行,避免直接在UI事件回调中处理耗时操作
  • 若上传启停通过ICommand绑定,使用AsyncCommand而非普通ICommand,确保命令执行逻辑是异步的,不会阻塞UI线程

2. 解决TaskCanceledException刷屏问题

主动取消操作触发的异常属于预期情况,无需让它刷屏控制台:

  • 在每个上传任务的异步逻辑中,针对性捕获OperationCanceledException(TaskCanceledException是其子类),仅记录必要日志,不向上抛出
  • 通过CancellationToken.IsCancellationRequested判断是否为主动取消,区分处理取消与其他异常:
try
{
    using var fileStream = file.OpenRead();
    await blobClient.UploadAsync(fileStream, cancellationToken);
}
catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested)
{
    // 主动取消,仅记录日志即可
    Debug.WriteLine($"[{file.Name}] 上传任务已取消");
}
catch (Exception ex)
{
    // 处理真实上传异常
    Debug.WriteLine($"[{file.Name}] 上传失败: {ex.Message}");
}

3. 区分已启动与待启动上传的取消逻辑

  • 已启动的上传:通过传递给UploadAsync的CancellationToken发送取消信号,Azure SDK会响应令牌并触发OperationCanceledException,按上述异常处理逻辑即可
  • 待启动的上传:维护一个线程安全的待处理任务队列(如ConcurrentQueue<FileInfo>),调用CancelPendingUploadsAsync时,清空队列并标记取消状态;在启动每个任务前先检查CancellationToken是否已取消,若已取消则跳过启动

4. 优化Task.Run的过度使用

如果StartSyncAsync已经在后台线程执行,UploadFilesAsync中无需用Task.Run包装上传任务:

  • UploadAsync本身是异步非阻塞方法,直接调用即可,避免不必要的线程切换
  • 确保所有异步方法都正确使用await,避免出现未等待的任务导致的上下文混乱

5. 正确控制多文件上传并发

Azure Blob SDK的TransferOptions.MaximumConcurrency控制的是单个大文件分块上传的并发数,而非多文件上传的并发数。要控制多文件上传的并发数,推荐以下两种方式:

方式一:使用Parallel.ForEachAsync(.NET 6+)

直接通过MaxDegreeOfParallelism参数控制同时上传的文件数,无需额外使用SemaphoreSlim:

await Parallel.ForEachAsync(filesToUpload, new ParallelOptions
{
    MaxDegreeOfParallelism = 5, // 设置并发上传的文件数量
    CancellationToken = cancellationToken
}, async (file, ct) =>
{
    var blobClient = containerClient.GetBlobClient(file.Name);
    using var stream = file.OpenRead();
    await blobClient.UploadAsync(stream, cancellationToken: ct);
});

方式二:正确使用SemaphoreSlim

如果坚持使用SemaphoreSlim,确保初始化计数与期望的并发数一致,且每个上传任务正确等待信号量:

var semaphore = new SemaphoreSlim(5); // 允许5个并发任务
var uploadTasks = new List<Task>();

foreach (var file in filesToUpload)
{
    if (cancellationToken.IsCancellationRequested) break;
    
    await semaphore.WaitAsync(cancellationToken);
    uploadTasks.Add(Task.Run(async () =>
    {
        try
        {
            var blobClient = containerClient.GetBlobClient(file.Name);
            using var stream = file.OpenRead();
            await blobClient.UploadAsync(stream, cancellationToken);
        }
        finally
        {
            semaphore.Release();
        }
    }, cancellationToken));
}

await Task.WhenAll(uploadTasks);

内容的提问来源于stack exchange,提问作者federico

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:26:06