打字机效果完整代码一、什么是流式输出普通AI调用等待 → 等待 → 等待 → 一次性返回全部结果流式输出开始返回 → 逐字/逐词输出 → 像打字机一样用户体验差距非常明显。这也是为什么 ChatGPT 用打字机效果而不是等全部生成完再显示。二、Semantic Kernel 流式输出 API核心方法是GetStreamingChatMessageContentsAsync和普通调用的对比// 普通调用一次性返回var result await chatService.GetChatMessageContentAsync(history);string content result.Content;// 流式调用逐块返回await foreach (var chunk in chatService.GetStreamingChatMessageContentsAsync(history)){string piece chunk.Content; // 每次一小块}三、在服务层封装流式方法使用 IAsyncEnumerable 作为返回类型配合 yield return 逐块返回内容using System.Runtime.CompilerServices;public async IAsyncEnumerable SummarizeStreamAsync(string content,[EnumeratorCancellation] CancellationToken ct default){var history new ChatHistory();history.AddUserMessage($“请总结以下内容\n{content}”);await foreach (var chunk in _chatService! .GetStreamingChatMessageContentsAsync( history, cancellationToken: ct)) { if (!string.IsNullOrEmpty(chunk.Content)) yield return chunk.Content; }}注意事项必须加 [EnumeratorCancellation] 特性才能正确处理取消操作过滤空 chunk避免UI无意义刷新CancellationToken 要透传支持用户取消四、在 WPF 里实时更新 UI关键点流式输出在后台线程产生数据需要用 Progress 或者直接更新绑定属性来刷新UI。因为 ObservableCollection 和INotifyPropertyChanged 的绑定属性在 WPF 里可以跨线程更新只要是通过属性setter更新的// 在处理循环里var sb new StringBuilder();await foreach (var chunk in_aiService.SummarizeStreamAsync(content, ct)){sb.Append(chunk);// 直接更新绑定属性WPF自动刷新UI item.Result sb.ToString();}item.Status ProcessStatus.Completed;五、完整测试代码控制台版先用控制台验证流式输出效果using Microsoft.SemanticKernel;using Microsoft.SemanticKernel.ChatCompletion;var builder Kernel.CreateBuilder();builder.AddOpenAIChatCompletion(modelId: “deepseek-ai/DeepSeek-V3”,apiKey: “你的硅基流动Key”,endpoint: new Uri(“https://api.siliconflow.cn/v1”));var kernel builder.Build();var chatService kernel.GetRequiredService();var history new ChatHistory();history.AddUserMessage(“用200字介绍一下人工智能的发展历史”);Console.WriteLine(“流式输出开始”);await foreach (var chunk in chatService.GetStreamingChatMessageContentsAsync(history)){if (!string.IsNullOrEmpty(chunk.Content))Console.Write(chunk.Content); // 不换行逐字追加}Console.WriteLine(“\n完成”);六、踩过的坑坑1忘记加 [EnumeratorCancellation]IAsyncEnumerable 方法里如果有CancellationToken 参数必须加 [EnumeratorCancellation] 特性否则取消操作不会正确传递。// ❌ 错误public async IAsyncEnumerable StreamAsync(CancellationToken ct default)// ✅ 正确public async IAsyncEnumerable StreamAsync([EnumeratorCancellation] CancellationToken ct default)坑2chunk.Content 可能为 null 或空流式返回的每个 chunkContent 属性可能是 null 或空字符串必须过滤// ❌ 不过滤可能空字符串刷新UIyield return chunk.Content;// ✅ 过滤空值if (!string.IsNullOrEmpty(chunk.Content))yield return chunk.Content;坑3StringBuilder 要在循环外声明// ❌ 每次chunk都新建结果只有最后一块await foreach (var chunk in …){var sb new StringBuilder(); // 错误位置sb.Append(chunk.Content);item.Result sb.ToString();}// ✅ 循环外声明累积所有内容var sb new StringBuilder(); // 正确位置await foreach (var chunk in …){sb.Append(chunk.Content);item.Result sb.ToString();}七、效果对比普通调用处理10秒钟 → 结果突然全部出现用户不知道有没有在运行很焦虑流式输出开始后立刻看到文字蹦出来用户直观感受到AI在工作体验好很多八、完整项目这是我做的「文省事」AI文档批量处理工具里用到的核心技术。工具功能批量生成文档摘要批量提取关键信息支持PDF/Word/TXT结果导出Excel感兴趣的可以咸鱼搜索「文省事」。如果本文对你有帮助点个赞后续持续更新C# AI实战内容。