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

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;
        }
    }
}

关键优化说明

  1. 移除同步阻塞调用:构造函数中不再使用GetAwaiter().GetResult()同步等待异步方法,改用异步初始化方法LoadMetricsAsync(),避免Android平台上异常上下文丢失导致的无法捕获问题。
  2. 统一HttpClient实现:使用SocketsHttpHandler替代Android默认的AndroidMessageHandler,消除跨平台的异常包装差异,确保超时等异常能被统一捕获。
  3. 完善异常区分:在OperationCanceledException中判断是超时还是手动取消,提升异常处理的精准性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 07:55:21