提问人:Bobby Bridgeman 提问时间:11/6/2023 最后编辑:Bobby Bridgeman 更新时间:11/7/2023 访问量:73
为什么这个 while 循环只运行一次然后冻结直到结果任务完成?Blazor Server c#
Why is this while loop only running once then freezing until the result task is complete? Blazor Server c#
问:
我有以下 Blazor Server 代码,该代码调用未等待的异步任务,而是使用 while 循环来检查任务的完成或异常。我遇到的问题是 while 循环似乎只运行一次,然后似乎只是停止。该任务没有出错,也没有完成,但一旦完成,它就会退出 while 循环并按预期继续。
谁能解释一下这里发生了什么?
private async Task Generate()
{
awaitingRequest = true;
StateHasChanged();
var result = gPT.SendCommand("system", bot.Description, apiKey, true);
try
{
loopCount = 0;
while (!result.IsCompleted && !result.IsFaulted)
{
//Update the UI after the asynchronous operation
await Task.Delay(500);
loopCount++;
StateHasChanged();
}
}
catch (Exception ex)
{
}
outputText = gPT.messages[1].content;
awaitingRequest = false;
StateHasChanged();
}
我尝试了各种断点,并将 loopCount 绑定到文本区域控件以进行视觉确认,两者都只是指示它在第二次调用 await Task.Delay(500) 后停止在 while 循环中。我尝试设置ConfigureAwait(false),但没有区别。我基本上希望在gPT.SendCommand方法运行时更新UI。我以前使用过这种方法,效果很好,所以我不知道是什么原因造成的。
答:
我认为问题是 gPT.SendCommand 虽然是一个异步任务,但没有在自己的线程上运行。使用 Task.Run 解决了这个问题。也许有人可以证实这一点。
var result = Task.Run(async () => await gPT.SendCommand("system", bot.Description, apiKey, true));
解释
- 调用该方法。它开始在 UI 线程上运行。
Generate()
- 该方法由 调用。任务在 UI 线程(与父方法相同的线程)上排队。
SendCommand()
Generate()
- 该方法经历 while 循环的第一次迭代并调用 ,从而生成线程。
Generate()
await Task.Delay(500)
- UI 线程开始处理其队列中的任何其他任务
- 任务被拾取
SendCommand
- 任务在完成之前不会屈服
SendCommand
- 这表明它正在轮询响应,而不是使用异步 IO
- 完成后,UI 线程将继续处理其队列中的任务,并返回到该任务
Generate
Stephen Toub 在他关于 ConfigureAwait 的文章中描述了类似的情况:
考虑一个库方法,该方法对某些网络下载的结果使用 await。调用此方法并同步阻止等待它完成,例如使用 。Wait() 或 .result 或 .GetAwaiter() 中。GetResult() 关闭返回的 Task 对象。现在考虑一下,如果当前 SynchronizationContext 将可在其上运行的操作数限制为 1 时,如果调用它会发生什么情况,无论是通过前面所示的 MaxConcurrencySynchronizationContext 之类的显式内容,还是通过仅具有一个可以使用的线程的上下文(例如 UI 线程)隐式调用它。因此,您可以在该线程上调用该方法,然后阻止它等待操作完成。该操作将启动网络下载并等待它。由于默认情况下等待 Task 将捕获当前的 SynchronizationContext,因此它会这样做,并且当网络下载完成时,它会将回调回 SynchronizationContext,该回调将调用操作的其余部分。但是,唯一可以处理排队回调的线程当前被代码阻塞阻止,等待操作完成。在处理回调之前,该操作不会完成。僵局!
为什么不改变任何东西?ConfigureAwait(false)
ConfigureAwait
更改等待
任务的行为。它确定延续('await' 关键字后面的所有内容)是否应在同一同步上下文(UI 线程)上运行,还是在线程池中的任何线程上运行。在以下示例中,第二次调用将在线程池中的默认线程上执行(并引发异常):StateHasChanged()
awaitingRequest = true;
StateHasChanged();
await gPT.SendCommand("system", bot.Description, apiKey, true).ConfigureAwait(false);
StateHasChanged();
在您的情况下,您没有执行任务,因此调用不起作用。await
ConfigureAwait
评论
SendCommand
ConfigureAwait(false)
gPT.SendCommand
评论
async void
async Task