Xamarin Forms无法将图片以Base64字符串上传至Cosmos DB(SDK3)
Xamarin Forms上传Base64头像到Azure Cosmos DB超时问题
我在学习Xamarin Forms,尝试将裁剪后的头像转为Base64字符串上传到Azure Cosmos DB时,出现RequestTimeout错误。APP内可以正常显示裁剪后的图片,但上传操作失败,错误日志如下:
DocDBTrace Warning: 0 : RNTBD call timed out on channel xxx.xxx.x.xx:xxxx -> xx.xx.xx.x:xxxxx. Error: SendTimeout DocDBTrace Error: 0 : DocumentClientException with status code RequestTimeout, message: Message: Request timed out. More info: https://aka.ms/cosmosdb-tsg-request-timeout, inner exception: Microsoft.Azure.Documents.TransportException: A client transport error occurred: Sending the request timed out. (Time: 2023-01-14T19:10:07.2121350Z, activity ID: 38c1b0d0-847e-4eff-bb41-dfc541489b24, error code: SendTimeout [0x000E], base error: HRESULT 0x80131500, URI: rntbd://cdb-ms-prod-xxxxpartitions/xxxxx/replicas/xxxxx/, connection: xxx.xxx.x.xx:xxxxx -> xx.xx.xx.x:xxxxx, payload sent: True) at Microsoft.Azure.Documents.Rntbd.Channel.RequestAsync (Microsoft.Azure.Documents.DocumentServiceRequest request, Microsoft.Azure.Documents.TransportAddressUri physicalAddress, Microsoft.Azure.Documents.ResourceOperation resourceOperation, System.Guid activityId, Microsoft.Azure.Documents.TransportRequestStats transportRequestStats) [0x0035a] in <ffe3c00d433342948947b1c93a852e2f>:0 at Microsoft.Azure.Documents.Rntbd.LoadBalancingPartition.RequestAsync (Microsoft.Azure.Documents.DocumentServiceRequest request, Microsoft.Azure.Documents.TransportAddressUri physicalAddress, Microsoft.Azure.Documents.ResourceOperation resourceOperation, System.Guid activityId, Microsoft.Azure.Documents.TransportRequestStats transportRequestStats) [0x001ad] in <ffe3c00d433342948947b1c93a852e2f>:0 at Microsoft.Azure.Documents.Rntbd.TransportClient.InvokeStoreAsync (Microsoft.Azure.Documents.TransportAddressUri physicalAddress, Microsoft.Azure.Documents.ResourceOperation resourceOperation, Microsoft.Azure.Documents.DocumentServiceRequest request) [0x0015c] in <ffe3c00d433342948947b1c93a852e2f>:0 , and response headers: null DocDBTrace Warning: 0 : ClientRetryPolicy: RequestTimeout. Failed Location: https://xxxxxx.documents.azure.com/; ResourceAddress: dbs/xxxxx/colls/xxxx/docs/6957c0bb-e3cb-4cc3-8b04-3bbef4b85c37 DocDBTrace Error: 0 : Operation will NOT be retried. Current attempt 0, Status Code: RequestTimeout
实现流程
- 使用ImageCropper.Forms库裁剪图片
- 将图片转为流后转成Base64字符串
- 调用Cosmos DB的ReplaceItemAsync接口更新用户数据
相关代码
图片读取与上传代码
var result = await MediaPicker.PickPhotoAsync(new MediaPickerOptions {}); if (result != null) { new ImageCropper() { CropShape = ImageCropper.CropShapeType.Oval, AspectRatioX = 1, AspectRatioY = 1, Success = (imageFile) => { Device.BeginInvokeOnMainThread(() => { Stream stream = File.OpenRead(imageFile); byte[] bytes; using (var memoryStream = new MemoryStream()) { stream.CopyTo(memoryStream); bytes = memoryStream.ToArray(); UserConnected.UserImage = MySharedAppClasses.BitmapToBase64(bytes); Device.BeginInvokeOnMainThread(() => { ProfileImage.Source = ImageSource.FromStream(() => new MemoryStream(MySharedAppClasses.Base64ToBitmap(UserConnected.UserImage))); }); // 上传头像 MySharedAppClasses.UpdateUserInfos(UserConnected); } }); } }.Show(this, result.FullPath); }
Cosmos DB上传函数
public async static Task<MyUser> UpdateUserAsync(MyUser myUser) { try { ItemResponse<MyUser> response = await container.ReplaceItemAsync(myUser, myUser.MyId.ToString()); return response.Resource; } catch (Exception e) { ErrorManager.InsertErrorAsync("update user error", e.Message); return null; } }
实体类定义
public class MyUser { [PrimaryKey, AutoIncrement] public int ID { get; set; } [JsonProperty(PropertyName = "id")] public Guid MyId { get; set; } [JsonProperty(PropertyName = "username")] public string Username { get; set; } [JsonProperty(PropertyName = "userimage")] public string UserImage { get; set; } }
解决方法
1. 压缩Base64图片大小
Base64编码会让图片体积增加约33%,如果裁剪后的图片仍然较大(比如超过1MB),会导致请求payload过大触发超时。可以在转Base64前对图片进行压缩:
// 依赖SkiaSharp NuGet包实现图片压缩 public static byte[] CompressImage(byte[] imageBytes, int quality = 50) { using (var inputStream = new MemoryStream(imageBytes)) using (var outputStream = new MemoryStream()) { var image = SKBitmap.Decode(inputStream); var encodedImage = image.Encode(SKEncodedImageFormat.Jpeg, quality); encodedImage.SaveTo(outputStream); return outputStream.ToArray(); } }
替换原有代码中的bytes = memoryStream.ToArray():
bytes = CompressImage(memoryStream.ToArray(), 50); // 质量值范围0-100,值越小压缩率越高
2. 调整Cosmos DB客户端超时设置
默认客户端超时可能不足以处理较大payload,初始化客户端时延长超时时间:
var client = new CosmosClient(connectionString, new CosmosClientOptions { RequestTimeout = TimeSpan.FromSeconds(30), MaxRetryAttemptsOnRateLimitedRequests = 5, MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan.FromSeconds(10) });
3. 避免在主线程执行网络操作
当前代码在主线程中直接调用上传接口,会阻塞主线程导致超时,应放到后台线程执行:
// 替换原有的上传代码 Task.Run(async () => { await MySharedAppClasses.UpdateUserInfos(UserConnected); });
4. 检查Cosmos DB配置
- 临时提高集合的吞吐量(RU/s)测试,排查是否是资源不足导致的超时
- 检查分区键设计,避免单分区负载过高引发热点问题
5. 启用重试策略
日志显示请求未触发重试,配置客户端重试策略应对临时网络波动:
var clientOptions = new CosmosClientOptions { RetryOptions = new CosmosRetryOptions { MaxRetryAttemptsOnThrottledRequests = 5, MaxRetryWaitTime = TimeSpan.FromSeconds(10) } };
内容的提问来源于stack exchange,提问作者hugo
相关产品推荐
相关产品推荐

