惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
T
Threatpost
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
V2EX - 技术
V2EX - 技术
D
Docker
G
Google Developers Blog
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
The GitHub Blog
The GitHub Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
T
Troy Hunt's Blog
Webroot Blog
Webroot Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
量子位
Apple Machine Learning Research
Apple Machine Learning Research
H
Help Net Security
F
Full Disclosure
B
Blog
O
OpenAI News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
Google DeepMind News
Google DeepMind News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
Forbes - Security
Forbes - Security
Know Your Adversary
Know Your Adversary
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
Scott Helme
Scott Helme
T
The Exploit Database - CXSecurity.com
博客园 - 聂微东
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
Recorded Future
Recorded Future
IT之家
IT之家
Project Zero
Project Zero
Stack Overflow Blog
Stack Overflow Blog
小众软件
小众软件
Attack and Defense Labs
Attack and Defense Labs
L
Lohrmann on Cybersecurity
SecWiki News
SecWiki News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

博客园 - China Soft

试官问:你用 AI 编程半年了,那怎么保证 Claude Code 写出来的代码是对的? 园友特惠| 1Panel 企业版 & AI 一体机限量放售,最高直降 5100! 上位机程序发布打包成安装包---Inno Setup 这是一篇学习笔记,分享给大家,如果大家喜欢,也会开启USB的专辑 我问了 AI 一个问题:编码能力贬值后,什么能力值钱? Visual Studio 2026(VS2026) 密钥/激活码 Dddify:给 ASP.NET Core 项目一套轻量、清晰、可落地的 DDD 基础设施 C# ESP32/STM32 轻量 Web 能力库:PicoServer.Nano 高性能百度OCR ONNX Runtime C#实现 别再说 AI 开发就是调接口了!5 种主流模式一次讲清 使用Cursor实现管理系统登录界面的快速开发 在 Avalonia 中编写高性能动画 傻子可懂的 Harness Engineering 入门教程 + 项目实战,一次搞懂 AI 编程工程化! 提升 Text2SQL 准确率 AI/Vibe Coding,本质是软件人工时代向软件工业时代发展 体验优先:十分钟使用 Python+LangChain 玩转阿里通义千问 大模型基础(二):必懂5大基础概念《Token、上下文窗口、Embedding、预训练、微调》 大模型基础(一):什么是LLM? Prompt、Agent、Function Call、Skill、MCP,傻傻分不清楚? 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 作为一线研发,我快被全网的 AI 焦虑逼疯了。 Qt QtWebEngine 白屏的解决方案 都是微软亲儿子,WPF凭啥干不掉WinForm?这3个场景说明白了 .NET 高级开发 | C# 中的动态代码:反射、EMIT、表达式树、Roslyn、Source Generators 大模型看大模型:推理Token的能耗用电量比对 WorkBuddy:从“我是谁”到“帮我干活” .NET 高级开发 | 开发 .NET 诊断工具、链路追踪原理 Prompt 焚诀——一个模板,终结你和 AI 的所有沟通问题 [项目]涉案资产信息化管理平台(山东某执法单位定制开发) 大模型RAG实战,从被骂不靠谱到成为部门MVP,这是我的踩坑全记录 白帽子为什么几乎都绕不开 httpx:一款 HTTP 资产探测工具的技术价值 从Prompt工程到Skill工程:Agent Skills开放标准彻底改变了AI协作方式 MAUI项目在Android平台通过U盘实现软件更新 微软竟然出了免费的 AI 应用开发课?!我已经学上了 一文彻底搞懂 OpenClaw 的架构设计与运行原理(万字图文) AI到底聪明在哪——从手机人脸识别说起 硬核科普:为何小米任天堂封杀 Root?揭秘操作系统从启动到“夺权”的底层游戏
C#实现控制台多区域输出
China Soft · 2026-06-18 · via 博客园 - China Soft

https://www.cnblogs.com/wucy/p/20292775/csharp-multi-region

近一年以来,AI Agent的发展速度非常快。
如果经常使用一些Agent CLI工具,例如 Claude Code、Gemini CLI、OpenCode 等产品,会发现它们有一个共同特点:
虽然运行在终端之中,但已经完全不是传统命令行程序的样子。
在执行任务过程中,它们通常会同时展示:

  • Agent执行状态
  • 思考过程
  • 文件变更信息
  • Token统计
  • 系统日志
  • 工具调用结果

整个终端界面被划分成多个独立区域,并且每个区域都在实时刷新。
例如下面这种布局:

┌────────────────────┬────────────────────┐
│ Agent状态          │ Token统计          │
│                    │                    │
├────────────────────┴────────────────────┤
│                                         │
│              执行过程区域               │
│                                         │
├─────────────────────────────────────────┤
│               系统日志                  │
└─────────────────────────────────────────┘

上次在微信群里看到黑洞大佬在做类似的Agent CLI谈到过控制台多区域输出的问题,我当时比较好奇:

C# 原生 Console 是如何实现多区域动态界面的呢?

经过一番研究之后发现,实现原理并没有想象中复杂。
本文通过一个简单示例,介绍如何利用 C# Console 实现:

  • 多区域布局
  • 动态内容刷新
  • 滚动日志窗口
  • 多线程安全输出
  • 优雅退出机制

Console为什么能够实现多区域输出#

大多数情况下,我们使用 Console 都是这样:

Console.WriteLine("任务开始");

for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"执行进度:{i}");
}

Console.WriteLine("任务结束");

输出结果如下:

任务开始
执行进度:0
执行进度:1
执行进度:2
...
任务结束

看起来控制台只能从上往下不断输出内容。
实际上 Console 还提供了一组非常重要的API:

Console.SetCursorPosition(x, y);

它允许程序直接控制光标位置。
例如:

Console.SetCursorPosition(10, 5);
Console.Write("Hello");

程序会直接在指定坐标位置输出内容。
也就是说:

Console ≠ 输出流

而更像是:

Console = 字符画布

只要能够控制坐标位置,就能够实现区域划分与动态刷新。
这也是所有终端UI框架最基础的实现原理。

实现控制台布局#

首先需要将控制台划分成多个区域。
本示例将控制台分成三个部分:

  • 左上区域显示系统时间
  • 右上区域显示任务进度
  • 下半区域显示运行日志

布局绘制代码如下:

static void DrawLayout()
{
    int width = Console.WindowWidth;
    int height = Console.WindowHeight;

    int midX = width / 2;
    int midY = height / 2;

    for (int y = 0; y < midY; y++)
    {
        SafeWrite(midX, y, "│");
    }

    for (int x = 0; x < width - 1; x++)
    {
        SafeWrite(x, midY, "─");
    }

    SafeWrite(2, 0, "[ 系统时间 ]");
    SafeWrite(midX + 2, 0, "[ 任务进度 ]");
    SafeWrite(2, midY + 1, "[ 运行日志 (滚动) ]");
}

运行之后界面如下:

image

整个布局没有使用任何第三方组件。
本质上就是利用字符绘制边框。

实现系统时间区域#

布局完成之后,实现左上角的时间显示区域。
代码如下:

static void UpdateRegion_Clock()
{
    while (_isRunning)
    {
        SafeWrite(
            2,
            2,
            DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

        Thread.Sleep(1000);
    }
}

运行效果:

2026-05-21 16:30:25

由于始终输出到同一个位置,因此每次刷新都会覆盖之前的内容。
从而形成动态更新时间的效果。

实现任务进度区域#

右上角区域用于模拟任务进度。

实现代码如下:

static void UpdateRegion_Progress()
{
    int progress = 0;
    int midX = Console.WindowWidth / 2;

    while (_isRunning)
    {
        progress = (progress + 1) % 101;

        int barWidth = 20;

        int filled =
            (int)(barWidth * (progress / 100.0));

        string bar =
            "[" +
            new string('█', filled) +
            new string(' ', barWidth - filled) +
            $"] {progress}%";

        SafeWrite(midX + 2, 2, bar);

        Thread.Sleep(50);
    }
}

运行效果如下:

[██████████████      ] 72%

这种实现方式和很多安装程序、下载工具中的进度条实现原理基本一致。

实现滚动日志窗口#

日志区域是整个示例最核心的部分。
如果简单使用:

Console.WriteLine();

日志会不断向下滚动。
很快就会占满整个控制台。
因此需要一个固定区域用于展示日志内容。
首先定义日志队列:

private static readonly Queue<LogEntry>
    _logQueue = new Queue<LogEntry>();

新增日志:

_logQueue.Enqueue(
    new LogEntry
    {
        Text = newLog,
        Color = color
    });

超过最大显示行数时移除旧日志:

while (_logQueue.Count > _maxLogLines)
{
    _logQueue.Dequeue();
}

然后重新绘制日志区域:

foreach (var log in _logQueue)
{
    Console.SetCursorPosition(2, currentY);
    Console.ForegroundColor = log.Color;
    Console.Write(log.Text);

    currentY++;
}

运行效果如下:

15:32:11.212 [INFO ] 初始化完成
15:32:11.518 [INFO ] 加载配置文件
15:32:11.802 [DEBUG] 创建任务
15:32:12.015 [WARN ] Token接近阈值
15:32:12.381 [ERROR] 请求超时
15:32:12.912 [INFO ] 自动重试成功

同时根据日志等级设置不同颜色:

static ConsoleColor GetLogLevelColor(string level)
{
    switch (level)
    {
        case "ERROR":
            return ConsoleColor.Red;
        case "WARN":
            return ConsoleColor.Yellow;
        case "DEBUG":
            return ConsoleColor.DarkGray;
        default:
            return ConsoleColor.Green;
    }
}

这样整个日志区域看起来就更接近真实系统运行效果。

多线程下的控制台竞争问题#

到这里,一个新的问题出现了。
当前程序存在三个后台线程:

  • 时间刷新线程
  • 进度刷新线程
  • 日志刷新线程

这些线程都会同时操作控制台。

例如:

Console.SetCursorPosition(x, y);
Console.Write(text);

如果多个线程同时执行,很容易出现输出错乱。
因此需要统一加锁。
首先定义控制台锁对象:

private static readonly object _consoleLock =
    new object();

然后封装安全输出方法:

static void SafeWrite(
    int x,
    int y,
    string text)
{
    lock (_consoleLock)
    {
        Console.SetCursorPosition(x, y);
        Console.Write(text);
    }
}

后续所有区域输出都通过该方法完成。
这样能够保证同一时刻只有一个线程修改控制台状态。
避免多个线程抢占光标位置导致界面错乱。

优雅退出机制#

很多控制台程序都会忽略退出逻辑。
例如按下 Ctrl+C 之后:

程序被强制终止

此时可能出现:

  • 光标未恢复
  • 输出颜色异常
  • 界面残留

因此示例中特意处理了退出流程。

首先监听 Ctrl+C:

Console.CancelKeyPress += (sender, e) =>
{
    e.Cancel = true;
    _isRunning = false;
};

然后所有后台线程统一监听运行状态:

while (_isRunning)
{
}

退出时执行清理操作:

static void CleanupConsole()
{
    Thread.Sleep(200);

    Console.ResetColor();
    Console.CursorVisible = true;
    Console.Clear();

    Console.SetCursorPosition(0, 0);

    Console.WriteLine("程序已优雅退出。");
}

这样无论通过普通按键还是 Ctrl+C 退出,控制台都能够恢复到正常状态。

完整程序启动逻辑#

整个程序的启动流程并不复杂。

主函数如下:

static void Main(string[] args)
{
    Console.CursorVisible = false;

    Console.Clear();
    DrawLayout();

    Task.Run(UpdateRegion_Clock);
    Task.Run(UpdateRegion_Progress);
    Task.Run(UpdateRegion_Logs);

    while (_isRunning)
    {
        if (Console.KeyAvailable)
        {
            Console.ReadKey(true);
            _isRunning = false;
        }

        Thread.Sleep(100);
    }

    CleanupConsole();
}

程序启动之后:

  1. 初始化控制台
  2. 绘制布局
  3. 启动多个后台任务
  4. 实时刷新各个区域
  5. 接收退出信号
  6. 执行清理工作

整体结构非常简单清晰。
由于篇幅有限,未能展示完整代码示例。博主已将源码实例上传至GitHub,有兴趣的同学可自行翻阅
https://github.com/softlgl/ConsoleMultiRegion

为什么成熟框架做得更好#

虽然原生 Console 可以实现多区域动态界面,但如果真正开发 Agent CLI 产品,通常不会直接操作坐标。
目前比较流行的终端UI框架包括:

  • Spectre.Console
  • Terminal.Gui
  • Ink

这些框架已经帮开发者处理好了:

  • 布局系统
  • 表格组件
  • 动态重绘
  • 窗口缩放适配

例如在 Spectre.Console 中,一个布局可能只需要几行代码:

var layout = new Layout()
    .SplitRows(
        new Layout("Header"),
        new Layout("Body"),
        new Layout("Footer"));

开发效率和清晰程度远高于手动维护区域坐标。
不过从实现原理来看,它们应该最终仍然离不开:

Console.SetCursorPosition()

以及区域重绘机制。
理解这些基础能力之后,再去阅读相关框架源码,会更容易理解其设计思想。

总结#

随着AI Agent的发展,越来越多工具开始采用CLI作为交互入口。
现代终端程序也逐渐从传统的:

输入 → 输出

演变为:

布局 → 交互 → 状态管理 → 动态刷新

本文通过一个简单示例演示了如何使用 C# Console 实现:

  • 多区域布局
  • 实时时钟
  • 动态进度条
  • 滚动日志窗口
  • 多线程安全输出
  • 优雅退出机制

从实现角度来看,其核心能力并不复杂。
本质上就是利用:

Console.SetCursorPosition()

配合:

线程同步
区域划分
状态管理
动态重绘

构建出一个具备实时交互能力的终端界面。
我个人一般比较喜欢研究基础的实现,因为很多看起来十分复杂的效果,最后追溯到底层实现,其实都离不开这些最基础的能力,也就是咱们常说的底层逻辑。