C# MAUI异步函数Windows可捕获异常,Android下无法捕获
跨平台HttpClient异常捕获问题(Windows/Android)
问题场景
以下代码在Windows调试环境中能正常捕获API请求的各类异常,但在Android平台运行时,TaskCanceledException无法被GetNumberOfThingsAsync方法中的try/catch捕获,直接抛出未处理异常:
原代码
public partial class MetricsPage : ContentPage { public MetricsPage() { InitializeComponent(); using var client = new HttpClient(); client.BaseAddress = new Uri("http://example.com"); client.Timeout = new TimeSpan(0, 0, 3); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Api-Key", "<my_key>"); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); totalCreatedThings.Text = GetNumberOfThingsAsync(client).GetAwaiter().GetResult(); } static async Task<string> GetNumberOfThingsAsync(HttpClient client) { try { HttpResponseMessage response = await client.GetAsync("/api/get_number_things").ConfigureAwait(false); if (response.IsSuccessStatusCode) { string numberOfThings = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return numberOfThings; } else { return string.Empty; } } catch (HttpRequestException hex) { System.Diagnostics.Debug.WriteLine(hex.Message); return string.Empty; } catch (OperationCanceledException oex) { System.Diagnostics.Debug.WriteLine(oex.Message); return string.Empty; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); return string.Empty; } } }
Android平台错误日志
[monodroid-net] Exception caught while cancelling connection: Java.Net.SocketException: Socket closed [monodroid-net] at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniEnvironment.g.cs:line 11884 [monodroid-net] at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeAbstractVoidMethod(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 17 [monodroid-net] at Java.Net.HttpURLConnectionInvoker.Connect() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net6.0/android-31/mcw/Java.Net.HttpURLConnection.cs:line 725 [monodroid-net] at Xamarin.Android.Net.AndroidMessageHandler.<>c__DisplayClass125_0.<ConnectAsync>b__0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 444 [monodroid-net] --- End of managed Java.Net.SocketException stack trace --- [monodroid-net] java.net.SocketException: Socket closed [monodroid-net] at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:394) [monodroid-net] at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230) [monodroid-net] at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212) [monodroid-net] at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436) [monodroid-net] at java.net.Socket.connect(Socket.java:646) [monodroid-net] at com.android.okhttp.internal.Platform.connectSocket(Platform.java:182) [monodroid-net] at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:145) [monodroid-net] at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116) [monodroid-net] at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186) [monodroid-net] at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128) [monodroid-net] at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97) [monodroid-net] at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289) [monodroid-net] at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232) [monodroid-net] at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465) [monodroid-net] at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131) [monodroid-net] [monodroid-net] --- End of managed Java.Net.SocketException stack trace --- [monodroid-net] java.net.SocketException: Socket closed [monodroid-net] at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:394) [monodroid-net] at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230) [monodroid-net] at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212) [monodroid-net] at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436) [monodroid-net] at java.net.Socket.connect(Socket.java:646) [monodroid-net] at com.android.okhttp.internal.Platform.connectSocket(Platform.java:182) [monodroid-net] at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:145) [monodroid-net] at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116) [monodroid-net] at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186) [monodroid-net] at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128) [monodroid-net] at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97) [monodroid-net] at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289) [monodroid-net] at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232) [monodroid-net] at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465) [monodroid-net] at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131) [monodroid-net] **System.Threading.Tasks.TaskCanceledException:** 'The request was canceled due to the configured HttpClient.Timeout of 3 seconds elapsing.'
解决方案
核心问题是同步阻塞异步方法导致异常上下文丢失,加上Android平台默认AndroidMessageHandler的异常包装逻辑差异,以下是修复后的代码及关键优化点:
修复后代码
public partial class MetricsPage : ContentPage { public MetricsPage() { InitializeComponent(); // 避免在构造函数中同步阻塞异步调用,改用异步初始化 _ = LoadMetricsAsync(); } private async Task LoadMetricsAsync() { // 使用SocketsHttpHandler统一跨平台HttpClient行为,替代Android默认的AndroidMessageHandler using var handler = new SocketsHttpHandler(); using var client = new HttpClient(handler); client.BaseAddress = new Uri("http://example.com"); client.Timeout = new TimeSpan(0, 0, 3); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Api-Key", "<my_key>"); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); totalCreatedThings.Text = await GetNumberOfThingsAsync(client); } static async Task<string> GetNumberOfThingsAsync(HttpClient client) { try { HttpResponseMessage response = await client.GetAsync("/api/get_number_things").ConfigureAwait(false); if (response.IsSuccessStatusCode) { string numberOfThings = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return numberOfThings; } else { return string.Empty; } } catch (HttpRequestException hex) { System.Diagnostics.Debug.WriteLine(hex.Message); return string.Empty; } catch (OperationCanceledException oex) { // 区分超时和手动取消请求 var errorMsg = oex.CancellationToken.IsCancellationRequested ? "请求被手动取消" : $"请求超时:{oex.Message}"; System.Diagnostics.Debug.WriteLine(errorMsg); return string.Empty; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); return string.Empty; } } }
关键优化说明
- 移除同步阻塞调用:构造函数中不再使用
GetAwaiter().GetResult()同步等待异步方法,改用异步初始化方法LoadMetricsAsync(),避免Android平台上异常上下文丢失导致的无法捕获问题。 - 统一HttpClient实现:使用
SocketsHttpHandler替代Android默认的AndroidMessageHandler,消除跨平台的异常包装差异,确保超时等异常能被统一捕获。 - 完善异常区分:在
OperationCanceledException中判断是超时还是手动取消,提升异常处理的精准性。
内容的提问来源于stack exchange,提问作者Manu Fraile
相关产品推荐
相关产品推荐

