基于.NET 7的MAUI项目点位与照片存储优化问题咨询
问题解决方案
1. 避免UI阻塞的大对象存储方案
客户端侧
- 异步分离UI与业务逻辑:所有耗时操作(对象克隆、数据上传)必须通过
await异步执行,禁止使用.Result或.Wait()阻塞UI线程。若有本地数据预处理(如照片压缩、点位格式化),可通过Task.Run将逻辑放到后台线程,再通过MainThread.BeginInvokeOnMainThread更新UI:// 后台处理数据,不占用UI线程 var processedData = await Task.Run(() => { var cloned = BigObject.Clone(); cloned.Points.Clear(); cloned.Photos.Clear(); return cloned; }); // 异步上传,UI保持响应 var simpleObj = await saveJustAsync(processedData); - 分批次上传:将点位、照片拆分为小批次(如20个点位/10张照片为一批),完成一批后再执行下一批,降低单次请求负载。
服务端侧
- EF批量操作优化:存储点位时用
DbContext.AddRange()替代循环Add(),减少EF跟踪开销;配合EF批量扩展包(如EFCore.BulkExtensions)提升提交效率:await _context.Points.AddRangeAsync(simpleObject.Points); await _context.BulkSaveChangesAsync(); // 批量提交减少数据库交互次数 - Azure Blob分块上传:针对大照片,使用分块上传API拆分文件为4MB-100MB的块并行上传,避免超时或资源占用过高:
var blobClient = _blobServiceClient.GetBlobContainerClient("photos").GetBlobClient(photoFileName); await blobClient.UploadAsync(photoStream, new BlobUploadOptions { TransferOptions = new StorageTransferOptions { MaximumConcurrency = 4, // 控制并发数,避免资源耗尽 InitialTransferSize = 4 * 1024 * 1024 // 初始块大小设为4MB } });
2. 获取存储实时状态的方案
客户端侧
- 用
IProgress<T>传递进度:定义进度模型,在上传过程中实时通知UI更新:// 进度模型 public class UploadProgress { public int TotalItems { get; set; } public int CompletedItems { get; set; } public string CurrentStatus { get; set; } } // 客户端调用时传入进度回调 var progress = new Progress<UploadProgress>(update => { // 在UI线程更新进度条、状态文本 ProgressBar.Value = (double)update.CompletedItems / update.TotalItems * 100; StatusLabel.Text = update.CurrentStatus; }); await savePointsAsync(simpleObject, progress); - 上传方法内更新进度:
public async Task savePointsAsync(SimpleObject obj, IProgress<UploadProgress> progress) { var total = obj.Points.Count; var completed = 0; foreach (var point in obj.Points) { await _pointService.SaveAsync(point); completed++; progress.Report(new UploadProgress { TotalItems = total, CompletedItems = completed, CurrentStatus = $"上传点位 {completed}/{total}" }); } }
服务端侧
- 建立任务状态表:创建
UploadTask实体记录每个上传任务的进度与状态:public class UploadTask { public Guid Id { get; set; } public string UserId { get; set; } public int TotalPoints { get; set; } public int CompletedPoints { get; set; } public int TotalPhotos { get; set; } public int CompletedPhotos { get; set; } public UploadStatus Status { get; set; } // 待处理/处理中/完成/失败 public DateTime CreatedTime { get; set; } } public enum UploadStatus { Pending, Processing, Completed, Failed } - 实时更新与状态查询接口:处理任务时实时更新
UploadTask,并提供API供客户端查询:// 更新进度 public async Task UpdateTaskProgress(Guid taskId, int completedPoints) { var task = await _context.UploadTasks.FindAsync(taskId); task.CompletedPoints = completedPoints; task.Status = UploadStatus.Processing; await _context.SaveChangesAsync(); } // 查询状态接口 [HttpGet("tasks/{taskId}")] public async Task<IActionResult> GetTaskStatus(Guid taskId) { var task = await _context.UploadTasks.FindAsync(taskId); return Ok(task); } - 可选:SignalR实时推送:通过SignalR建立长连接,服务端主动推送进度变化,替代客户端轮询,提升实时性。
3. 多用户存储的队列机制实现
服务端侧
- Azure Queue Storage实现任务队列:
- 客户端提交请求时,服务端先创建
UploadTask并返回taskId,再将任务信息序列化放入队列:var queueClient = _queueServiceClient.GetQueueClient("upload-tasks"); await queueClient.CreateIfNotExistsAsync(); var message = JsonSerializer.Serialize(new { TaskId = task.Id, ObjectId = simpleObj.Id }); await queueClient.SendMessageAsync(message); - 后台Worker监听队列,取出任务后异步处理存储逻辑:
while (true) { var response = await queueClient.ReceiveMessageAsync(); if (response.Value == null) { await Task.Delay(1000); continue; } try { var taskData = JsonSerializer.Deserialize<TaskMessage>(response.Value.Body.ToString()); // 执行点位、照片存储,同时更新UploadTask状态 await ProcessUploadTask(taskData.TaskId); await queueClient.DeleteMessageAsync(response.Value.MessageId, response.Value.PopReceipt); } catch (Exception ex) { // 异常处理:限制重试次数后重新入队 await queueClient.UpdateMessageAsync(response.Value.MessageId, response.Value.PopReceipt, TimeSpan.FromMinutes(5)); } }
- 客户端提交请求时,服务端先创建
- 并发控制与限流:在Worker中用
SemaphoreSlim限制同时处理的任务数;针对用户设置并发上限(如每个用户最多同时处理2个上传任务),避免资源耗尽。 - 乐观锁避免冲突:在
UploadTask中添加Timestamp字段,EF自动用其实现乐观锁,防止多线程同时修改同一任务状态:
DbContext配置:public byte[] Timestamp { get; set; }modelBuilder.Entity<UploadTask>().Property(t => t.Timestamp).IsRowVersion();
内容的提问来源于stack exchange,提问作者Mafyou
相关产品推荐
相关产品推荐

