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

取消HTTP请求时触发java.net.SocketException的原因与处理

HttpClient取消请求抛出SocketException问题解析

问题概述

使用httpClient.SendAsync()以流形式下载文件时,通过CancellationToken取消请求,抛出的是java.net.SocketException: Socket closed异常,而非预期的OperationCanceledException。未手动操作Socket,所有Socket相关逻辑由Android底层HttpClient实现处理。


异常产生原因

这是Xamarin.Android旧版本的特定行为:

  • Xamarin.Android的AndroidClientHandler底层依赖Java的HttpURLConnection实现HTTP请求。
  • 当触发CancellationToken取消请求时,底层Java实现会直接关闭Socket连接,抛出SocketException,而没有将其包装为.NET标准的OperationCanceledException。
  • 堆栈跟踪中的提示[monodroid] Not wrapping exception of type Java.Net.SocketException from method SendAsync. This will change in a future release明确说明,该行为会在后续版本中修复,未来会将Java异常包装为对应的.NET取消异常。

正确处理方式

  1. 区分取消导致的异常:在捕获异常时,同时检查CancellationToken的IsCancellationRequested状态,或者判断异常消息是否包含Socket closed,将这类异常当作取消操作处理:
    catch (Java.Net.SocketException ex)
    {
        if (cancelToken.IsCancellationRequested || ex.Message.Contains("Socket closed"))
        {
            // 处理取消逻辑,比如提示用户下载已取消
            System.Diagnostics.Debug.WriteLine("下载已取消");
        }
        else
        {
            // 处理其他Socket错误,比如网络断开
            System.Diagnostics.Debug.WriteLine($"网络错误: {ex.Message}");
        }
    }
    catch (OperationCanceledException)
    {
        // 处理标准取消异常(新版本Xamarin会触发)
        System.Diagnostics.Debug.WriteLine("下载已取消");
    }
    catch (Exception ex)
    {
        // 处理其他异常
        System.Diagnostics.Debug.WriteLine($"未知错误: {ex.Message}");
    }
    
  2. 升级Xamarin版本:如果项目允许,升级到较新的Xamarin.Android版本,新版本已经修复了异常包装问题,取消请求时会抛出标准的OperationCanceledException。
  3. 统一异常处理逻辑:将SocketException(取消场景)和OperationCanceledException的处理逻辑合并,保证代码在新旧版本下都能正常工作。

是否可忽略该异常

不能直接忽略所有SocketException,但确认是取消操作导致的Socket closed异常可以忽略。需要注意区分:

  • 取消操作导致的:属于预期行为,可按取消逻辑处理,无需报错。
  • 其他原因导致的Socket异常(如网络中断、服务器断开):属于真实错误,需要处理或提示用户。

代码操作中的问题

给出的代码存在以下优化点:

  1. 避免频繁创建HttpClient:每次点击都new HttpClient()会导致Socket资源耗尽,建议复用HttpClient实例(比如声明为类级别的静态变量):
    private static readonly HttpClient _httpClient = new HttpClient();
    
  2. 避免使用async void:按钮点击事件用async void可能导致异常无法被正确捕获,建议改用ICommand绑定异步命令,或者保留async void但确保异常处理覆盖全面。
  3. CancellationTokenSource的生命周期管理:当前代码在取消后重置Cts是正确的,但需要确保请求正常完成后也重置Cts,避免下次请求使用已取消的令牌。

完整堆栈跟踪

[monodroid] Not wrapping exception of type Java.Net.SocketException from method `SendAsync`. This will change in a future release.
[0:]   at Java.Interop.JniEnvironment+InstanceMethods.CallIntMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00068] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniEnvironment.g.cs:11725 
  at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualInt32Method (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x00024] in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:229 
  at Java.Net.HttpURLConnection.get_ResponseCode () [0x00000] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-31/mcw/Java.Net.HttpURLConnection.cs:511 
  at Xamarin.Android.Net.AndroidClientHandler+<>c__DisplayClass46_0.<DoProcessRequest>b__2 () [0x00000] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs:438 
  at System.Threading.Tasks.Task`1[TResult].InnerInvoke () [0x0000f] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs:534 
  at System.Threading.Tasks.Task.Execute () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corert/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:2319 
--- End of stack trace from previous location where exception was thrown ---

  at Xamarin.Android.Net.AndroidClientHandler.DoProcessRequest (System.Net.Http.HttpRequestMessage request, Java.Net.URL javaUrl, Java.Net.HttpURLConnection httpConnection, System.Threading.CancellationToken cancellationToken, Xamarin.Android.Net.AndroidClientHandler+RequestRedirectionState redirectState) [0x00328] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs:438 
  at Xamarin.Android.Net.AndroidClientHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00286] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs:287 
  at System.Net.Http.HttpClient.FinishSendAsyncBuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) [0x0017e] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Net.Http/src/System/Net/Http/HttpClient.cs:506 
  at TestHttpClientSocketException.MainPage.startButton_Clicked (System.Object sender, System.EventArgs e) [0x00113] in C:\Projects\TestHttpClientSocketException\TestHttpClientSocketException\TestHttpClientSocketException\MainPage.xaml.cs:43 
  --- End of managed Java.Net.SocketException stack trace ---
java.net.SocketException: Socket closed
    at java.net.SocketInputStream.read(SocketInputStream.java:203)
    at java.net.SocketInputStream.read(SocketInputStream.java:139)
    at com.android.okhttp.okio.Okio$2.read(Okio.java:141)
    at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:211)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:316)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:310)
    at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:206)
    at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:395)
    at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:144)
    at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:887)
    at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:759)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:499)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:435)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:566)

XAML示例

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TestHttpClientSocketException.MainPage">

    <StackLayout>
        <Button x:Name="startButton" Text="Download" Clicked="startButton_Clicked" />
        <Button x:Name="cancelButton" Text="Cancel" Clicked="cancelButton_Clicked" />
    </StackLayout>

</ContentPage>

后端代码示例

public partial class MainPage : ContentPage
{
    CancellationTokenSource cancelToken;

    public MainPage()
    {
        InitializeComponent();
        this.cancelToken = new CancellationTokenSource();
    }

    private async void startButton_Clicked(object sender, EventArgs e)
    {
        HttpResponseMessage response;

        try
        {
            using (var httpClient = new HttpClient())
            {
                HttpMethod method = HttpMethod.Post;
                string requestUri = "http://0.0.0.0:80/some/uri";
                httpClient.Timeout = new TimeSpan(0, 0, 300);
                HttpContent httpContent = new StringContent("<soapenv:Envelope ...");
                HttpRequestMessage req = new HttpRequestMessage(method, requestUri);
                req.Headers.Add("SOAPAction", "http://schemas.xmlsoap.org/soap/envelope");

                req.Method = method;
                req.Content = httpContent;
                req.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/xml" + "; charset=utf-8");
                response = await httpClient.SendAsync(req, this.cancelToken.Token).ConfigureAwait(false);
                
                System.Diagnostics.Debug.WriteLine("finished");
            }
        } catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.StackTrace);
        }
        
    }

    private void cancelButton_Clicked(object sender, EventArgs e)
    {
        this.cancelToken.Cancel();
        this.cancelToken = new CancellationTokenSource();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 13:50:45