.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>
解决方案
问题根源
- Android严格模式禁止在主线程执行网络IO操作,原代码中
streamReader.EndOfStream是同步属性,内部会触发网络读取,导致异常。 Thread.Sleep(170)是同步阻塞,会卡住主线程,同时破坏异步流程。- 使用
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
相关产品推荐
相关产品推荐

