最近写的一个async函数:
Public Async Function AfterFirstChat(ByVal strSendMessage As String) As Task(Of String)
Try
If strSendMessage = "" Then
strModOllAmaErrString = STRcurErrHead & "没有提供要发送的message。"
Return ""
End If
Dim builder As New StringBuilder
' Create a new PromptExecutionSettings object
Dim executionSettings As New OpenAIPromptExecutionSettings()
' User question & answer loop
Chat.AddUserMessage(strSendMessage)
builder.Clear()
' Get the AI response streamed back to the console
Dim asyncEnumerator As IAsyncEnumerator(Of StreamingChatMessageContent) = Ai.GetStreamingChatMessageContentsAsync(Chat, executionSettings, kKernel).GetAsyncEnumerator()
Try
While Await asyncEnumerator.MoveNextAsync()
Try
Dim message As StreamingChatMessageContent = asyncEnumerator.Current
builder.Append(message.Content)
Catch ex As Exception
End Try
End While
Catch ex As Exception
End Try
Chat.AddAssistantMessage(builder.ToString())
Return builder.ToString()
Catch ex As Exception
Return ""
End Try
End Function
然后调用时:
如果使用:
Return ModOllAmaAfterFirstChat(strFirstChat).result
在执行到函数的:
While Await asyncEnumerator.MoveNextAsync()
语句时,就会死锁,始终得不到回应。
而使用:
Return Await ModOllAmaAfterFirstChat(strFirstChat)
就一切正常。
唯一的区别就是调用async函数的方式不同。
然后大量查询资料,终于发现了秘密。
如以下例子:
static void Main(string[] args)
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:start");
Test(); //不等待
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:end");
Console.Read();
}
static Task<int> Sleep()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Sleep start");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Sleep end");
return Task.FromResult(100);
}
static Task<int> Test()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Test start");
var a=Task.Run(Sleep).Result;//由上一篇文章可知Run将会在线程池内调度执行
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Test end");
return Task.FromResult(a);
}
运行结果:
1:start
1:Test start
4:Sleep start
4:Sleep end
1:Test end
1:end
由此可以看出Task.Result会阻塞主线程(Main函数),主线程必须等待被调用的子函数(最终被调用的子函数时是一个async函数:Sleep)执行完成后才能继续执行。
而如果是这样写:
static void Main(string[] args)
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:start");
TestAsync();//不等待
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:end");
Console.Read();
}
static Task<int> Sleep()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Sleep start");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Sleep end");
return Task.FromResult(100);
}
static async Task<int> TestAsync()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Test start");
var a = await Task.Run(Sleep);
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:Test end");
return a;
}
则运行结果是:
1:start
1:Test start
4:Sleep start
1:end
4:Sleep end
4:Test end
应当注意到,这儿没有等待Test函数执行完成,主函数main就执行完成了,同样主函数更没有等待Sleep函数执行完成就执行完成了。
在看到这个例子之前,我看到的文章都说,一般情况下使用.result的写法多数会造成主线程阻塞,推荐使用await的写法,我还在想,是不是把之前的那些 函数调用写法都修改成await写法。
现在一看,其实是各有千秋,各有特色,我想这也是两种调用方法都存在的原因。
作此笔记的目的就是防止自己今后又记混。
