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

.NET MAUI Blazor读取响应流触发NetworkOnMainThreadException求助

.NET MAUI Blazor(.NET7)NetworkOnMainThreadException错误修复

问题说明

在.NET7的.NET MAUI Blazor项目中,读取流式响应时触发Android.OS.NetworkOnMainThreadException错误。尝试用Task.Run包裹逻辑后,仅能获取响应的第一个字符,无法拿到完整内容。

后端代码(原错误代码)

string requestRoot = JsonConvert.SerializeObject(requestToSerialize);
var content = new StringContent(requestRoot, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, _httpClient.BaseAddress)
{
    Content = content
};
var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var streamReader = new StreamReader(await response.Content.ReadAsStreamAsync());
var sb = new StringBuilder();
try
{
    while (!streamReader.EndOfStream)
    {
        var line = await streamReader.ReadLineAsync();

        if (line!.StartsWith("data:"))
        {
            if (!line.Contains("[DONE]"))
            {
                var data = line.Substring(5).Trim();
                WEBAPP.Entities.Root root = JsonConvert.DeserializeObject<WEBAPP.Entities.Root>(data);

                if (root.choices[0].delta.content != null)
                {
                    RequestLog.content = root.choices[0].delta.content;
                }
                if (root.choices[0].delta.role != null)
                {
                    RequestLog.role = root.choices[0].delta.role;
                }
                RequestLog.index = root.choices[0].index;


                if (data == "[DONE]")
                {
                    break;
                }

                JObject dataObj = JObject.Parse(data);
                string contentValue = (string)dataObj["choices"][0]["delta"]["content"];
                Response += contentValue;
                StateHasChanged();
                Thread.Sleep(170);

                Console.WriteLine(line);

                sb.Append(contentValue);
            }
        }
    }
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

错误信息

{Android.OS.NetworkOnMainThreadException: Exception of type 'Android.OS.NetworkOnMainThreadException' was thrown.
   at Java.Interop.JniEnvironment.InstanceMethods.CallIntMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 11725
   at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualInt32Method(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 511
   at Java.IO.InputStream.Read(Byte[] b, Int32 off, Int32 len) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net7.0/android-33/mcw/Java.IO.InputStream.cs:line 282
   at Android.Runtime.InputStreamInvoker.Read(Byte[] buffer, Int32 offset, Int32 count) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/InputStreamInvoker.cs:line 89
   at System.IO.BufferedStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.DelegatingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.StreamReader.ReadBuffer()
   at System.IO.StreamReader.get_EndOfStream()
   at CHAT.Pages.Index.SendRequest() in C:\Users\Mr.Sepi0l\Desktop\MAUI\CHAT\CHAT\Pages\Index.razor.cs:line 85
  --- End of managed Android.OS.NetworkOnMainThreadException stack trace ---
android.os.NetworkOnMainThreadException
    at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1667)
    at com.android.org.conscrypt.Platform.blockGuardOnNetwork(Platform.java:431)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:836)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:824)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:797)
    at com.android.okhttp.okio.Okio$2.read(Okio.java:138)
    at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
    at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
    at com.android.okhttp.internal.http.Http1xStream$ChunkedSource.readChunkSize(Http1xStream.java:453)
    at com.android.okhttp.internal.http.Http1xStream$ChunkedSource.read(Http1xStream.java:437)
    at com.android.okhttp.okio.RealBufferedSource$1.read(RealBufferedSource.java:372)
    at mono.java.lang.RunnableImplementor.n_run(Native Method)
    at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
    at android.os.Handler.handleCallback(Handler.java:942)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7872)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

  --- End of managed Android.OS.NetworkOnMainThreadException stack trace ---
android.os.NetworkOnMainThreadException
    at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1667)
    at com.android.org.conscrypt.Platform.blockGuardOnNetwork(Platform.java:431)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:836)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:824)
    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:797)
    at com.android.okhttp.okio.Okio$2.read(Okio.java:138)
    at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
    at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
    at com.android.okhttp.internal.http.Http1xStream$ChunkedSource.readChunkSize(Http1xStream.java:453)
    at com.android.okhttp.internal.http.Http1xStream$ChunkedSource.read(Http1xStream.java:437)
    at com.android.okhttp.okio.RealBufferedSource$1.read(RealBufferedSource.java:372)
    at mono.java.lang.RunnableImplementor.n_run(Native Method)
    at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
    at android.os.Handler.handleCallback(Handler.java:942)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7872)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
}

前端代码

@page "/"


<div class="container">
    <div class="response-container" style="padding:10px;">
        @if(Question != "")
        {
            <div class="card" style="background-color:#343541;">
                <h6 class="card-header" style="background-color:#59595c;color:white; text-align:center;">@Question</h6>
                <div class="card-body">
                    <p class="card-text">
                        @if (!string.IsNullOrEmpty(Response))
                        {
                            <pre>
                                 <code>
                                   @((MarkupString)RenderMarkdown(Response))
                                 </code>
                            </pre>
                        }
                    </p>
                </div>
            </div>
        }
    </div>
</div>
<div class="container-fluid">
    <div class="edit-form">
        <EditForm id="editForm" Model="@Message" OnValidSubmit="SendRequest" OnInvalidSubmit="Invalid" class="edit-form">
            <DataAnnotationsValidator />
            <p>
                <InputTextArea 
                    @onkeyup="Enter"
                    @oninput="Input"
                               @bind-Value="message"
                    style="background-color:#444654;color:#dcce85;font-weight:bold;" name="message" class="form-control" placeholder="Ask question" 
                   >
                </InputTextArea>
            </p>
            <div class="row">
                <div class="col">
                    <div class="d-grid gap-2">
                        <button class="btn form-button" type="submit">Send</button>
                    </div>
                </div>
                <div class="col">
                    <div class="d-grid gap-2">
                        <button class="btn form-button" @onclick="Resend" type="button">Resend</button>
                    </div>
                </div>
                <div class="col">
                    <div class="d-grid gap-2">
                        <button class="btn form-button" @onclick="NewChat" type="button">Reset</button>
                    </div>
                </div>
            </div>
        </EditForm>
    </div>
</div>

解决方案

问题根源

  1. Android严格模式禁止在主线程执行网络IO操作,原代码中streamReader.EndOfStream是同步属性,内部会触发网络读取,导致异常。
  2. Thread.Sleep(170)是同步阻塞,会卡住主线程,同时破坏异步流程。
  3. 使用Task.Run时,直接在后台线程调用StateHasChanged()无法正确更新UI,因为Blazor组件必须在UI线程执行状态更新。

修改后的代码

string requestRoot = JsonConvert.SerializeObject(requestToSerialize);
var content = new StringContent(requestRoot, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, _httpClient.BaseAddress)
{
    Content = content
};
var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var streamReader = new StreamReader(await response.Content.ReadAsStreamAsync());
var sb = new StringBuilder();

try
{
    string line;
    // 用异步读取返回null判断流结束,替代同步的EndOfStream检查
    while ((line = await streamReader.ReadLineAsync()) != null)
    {
        if (line.StartsWith("data:"))
        {
            if (line.Contains("[DONE]"))
            {
                break;
            }
            
            var data = line.Substring(5).Trim();
            if (string.IsNullOrEmpty(data)) continue;
            
            WEBAPP.Entities.Root root = JsonConvert.DeserializeObject<WEBAPP.Entities.Root>(data);

            if (root.choices[0].delta.content != null)
            {
                RequestLog.content = root.choices[0].delta.content;
            }
            if (root.choices[0].delta.role != null)
            {
                RequestLog.role = root.choices[0].delta.role;
            }
            RequestLog.index = root.choices[0].index;

            JObject dataObj = JObject.Parse(data);
            string contentValue = (string)dataObj["choices"][0]["delta"]["content"];
            if (!string.IsNullOrEmpty(contentValue))
            {
                // 回到UI线程更新状态
                await InvokeAsync(() =>
                {
                    Response += contentValue;
                    StateHasChanged();
                });
                // 用异步延迟替代同步Sleep,避免阻塞线程
                await Task.Delay(170);
                
                Console.WriteLine(line);
                sb.Append(contentValue);
            }
        }
    }
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

关键修改点

  • 移除!streamReader.EndOfStream同步检查,改用await streamReader.ReadLineAsync() != null判断流结束,避免主线程触发网络操作。
  • 替换Thread.Sleep(170)为await Task.Delay(170),保持异步流程不阻塞。
  • 更新UI时使用await InvokeAsync(() => { ... }),确保状态更新在UI线程执行,解决Task.Run后UI无法正确更新的问题。
  • 增加空值判断,避免处理空的data字段导致异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 16:29:57