VB.NET中async函数调用时使用await与.result的区别

最近写的一个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写法。

现在一看,其实是各有千秋,各有特色,我想这也是两种调用方法都存在的原因。

作此笔记的目的就是防止自己今后又记混。