Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/01-basic/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
01-basic 问答题
Q1.1
1. 分割与映射:使用 `CsvHelper` 库的 `LogRecordMap` 类,通过 `Map(m => m.LineNo).Index(0)` 指定第 0 列为行号,第 1 列时间戳,第 2 列 Pod 名,第 3 列为 Message。

2. 类型判断:在 `LineParser.ParseLine` 方法中,通过 `JsonDocument.Parse` 解析 `Message`,再用 `TryGetProperty("event", out var eventElement)` 获取事件字段,最后用 `switch` 匹配 `"call"`、`"request"`、`"internal"`。

3. JSON 解析方法:调用 `System.Text.Json.JsonSerializer.Deserialize<T>()` 将 JSON 转为强类型对象。

4. 防止字段缺失:在 `CallMessage`、`RequestMessage` 等 record 的属性上标注 `[JsonRequired]`,确保字段存在。

5. 命名转换:通过 `JsonSerializerOptions` 设置 `PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower`,自动映射 `RequestId` ↔ `request-id`。

Q1.2

调用链:
1. `KeyValueVisitor.Dump(LogEntry entry)`
2. `CallLogEntry.Accept<TResult>(ILogEntryVisitor<TResult> visitor)`(多态匹配实际类型)
3. `KeyValueVisitor.Visit(CallLogEntry entry)`

Q1.3.b
交互过程:提供了题目和代码文件,要求进行逐行讲解和代码实现指导,并在环境配置(WSL 安装 .NET 10 SDK)上获得了操作命令帮助。
AI 优点:AI 相比我自行查阅文档,AI 能结合具体任务提供更加定制化方案,节省了寻找合适 API 的时间。同时对代码可以有更详细地讲解,降低了我的理解门槛。
AI不足:存在幻觉没看到的文档/内容也会自己生成。
35 changes: 35 additions & 0 deletions docs/02-multithreading/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
02-multithreading 问答题

Q2.1

1. WorkQueue<T> 类中的共享变量有哪些?是通过什么保护其免于数据竞争的?
- 共享变量有:
- `_items`(队列本身,存储元素)
- `_isCompleted`(布尔标志,表示是否已停止添加)
保护方式:使用 `lock (_lock)` 对上述变量的所有读写操作进行互斥。`_lock` 是一个专用锁对象,确保同一时刻只有一个线程能进入临界区。

2. LogFileAnalyzer 类中的共享变量有哪些?是通过什么保护其免于数据竞争?
- 共享变量有:
`_currentDirectory`(当前目录路径)
`_isAnalyzing`(是否正在分析)
`_logFiles`(文件字典,文件名→FileInfo)
`_analysisResults`(解析结果字典,文件名→AnalysisResult)
- 保护方式:
使用 `lock (_syncRoot)` 保护所有对这些字段的读写操作
在 `WorkerMain` 中写入 `_analysisResults` 时也使用 `lock (_syncRoot)`,因为多个工作线程可能同时更新结果字典。

3. 如果条件变量的判断条件使用了 `if` 判断而非 `while` 判断,当出现虚假唤醒现象时,会出现什么后果?结合无限仓库容量的生产者消费者问题简单叙述。
如果使用 `if`,线程被唤醒后不会再次检查条件,直接执行后续操作。可能出现:
仓库为空但被唤醒.消费者以为有商品,执行 `Dequeue`,但队列中实际为空,导致抛出异常或取出无效数据。使用 `while` 则可以确保被唤醒后重新检查条件。
Q2.2

1. 那一段代码扫描了给定的目录中的全部 `.log` 后缀的日志文件?
在 `LogFileAnalyzer.ChangeDirectory` 方法中,有以下代码:
```csharp
var logFiles = Directory.EnumerateFiles(directoryPath, "*.log", SearchOption.TopDirectoryOnly)
.Select(filePath => Path.GetFileName(filePath))
.OrderBy(fileName => fileName);
2. 假使给定的需求是不仅要扫描给定目录中的日志文件,还要递归地获取给定的目录的全部子目录、子子目录……内的日志文件,应当如何做(简要回答即可)?
将 SearchOption.TopDirectoryOnly 改为 SearchOption.AllDirectories,即可递归扫描所有子目录。
Q2.3.b
我AI提供了本节 guidance.md 的任务描述,并附上已有的WorkQueue.cs、LogFileAnalyzer.cs、Program.cs 等代码文件,特别是多线程中的lock、Monitor.Wait/Pulse操作希望获得指导。在最初给出的WorkerMain代码中,AI将保存结果的操作放在了 catch 块内部,导致成功解析的结果没有被保存。经测试发现后,AI 将错误并修正。难度中等偏上。
Binary file added docs/03-async-grpc/image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/03-async-grpc/image-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/03-async-grpc/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 74 additions & 0 deletions docs/03-async-grpc/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
## T3.2 RemoteCli 功能说明

本次 T3.2 实现了一个远程命令行客户端 `RemoteCli`,通过 gRPC 协议与 Agent 服务端通信,实现了以下功能:

### 功能列表

| 菜单项 | 功能 | 对应 RPC 方法 |
|--------|------|---------------|
| 1 | 显示当前目录下的所有 `.log` 文件 | `GetLogFiles` |
| 2 | 分析指定的日志文件(支持多文件) | `AnalyzeFiles` |
| 3 | 分析当前目录下的所有日志文件 | `AnalyzeAll` |
| 4 | 查看指定文件的分析结果 | `GetAnalysisResult`(流式) |
| 5 | 切换日志文件目录 | `ChangeDirectory` |
| 6 | 退出程序 | - |

### 关键实现

1. **异步调用**:所有 gRPC 调用均使用 `Async` 后缀的异步方法(如 `client.AnalyzeFilesAsync`),配合 `await` 关键字实现非阻塞等待。

2. **流式处理**:`GetAnalysisResult` 使用 `responseStream.ReadAllAsync()` 逐条读取服务端返回的 `LogEntry`,先接收 `Header`(文件信息),再逐条接收日志条目。

3. **鲁棒性设计**:
- 用户输入非法目录路径时,提示 `DirectoryNotFound` 并重新等待输入
- 用户输入不存在的文件名时,提示 `FileNotFound`
- 用户输入空字符串时,提示重新输入
- 并行度输入负数时,提示不能为负数
- 所有可能抛出异常的代码都包裹了 `try-catch`

4. **类型转换**:使用 `GrpcTypeConverter.ConvertFromGrpc` 将 Protobuf 消息转回 C# 对象,再用 `KeyValueVisitor` 输出键值对。
![alt text](image-1.png)
![alt text](image-2.png)

### Q3.1:网络应用程序开发与普通应用程序开发的区别

#### 开发区别

| 方面 | 普通应用程序 | 网络应用程序 |
|------|-------------|-------------|
| 运行环境 | 单机运行,只依赖本地资源 | 需要服务端和客户端同时运行 |
| 数据存储 | 本地内存或文件 | 远程服务 + 本地缓存 |
| 错误来源 | 代码逻辑错误、本地资源不足 | 网络超时、服务端错误、序列化错误等 |
| 调试方式 | 直接运行,单步调试 | 需要同时启动服务端和客户端 |

#### 额外难点

1. **网络不可靠**:网络请求可能超时、失败、重发,需要处理各种异常情况。
2. **数据序列化/反序列化**:数据需要在 C# 对象和 Protobuf 消息之间转换,类型必须匹配。
3. **服务状态管理**:Agent 作为有状态服务,需要维护目录路径、分析结果等状态,多个 RPC 调用之间共享状态。
#### 个人感受
网络编程对于程序的鲁棒性要求更高,同时还要考虑与其他设备连接交互的问题,更加复杂。
### Q3.2.b:AI 使用情况

**1. 我的提示词**

我向 AI 提供了本节 `guidance.md` 的任务描述,并附上已有的 `GrpcLogEntryVisitor.cs`、`GrpcTypeConverter.cs`、`AgentService.cs`、`AgentSession.cs` 代码文件,要求逐行讲解每一段代码的含义,特别是异步编程中的 `async`/`await`、gRPC 的流式调用、Protobuf 类型转换等概念。

**2. AI 的使用方式**
- 让 AI 解释代码框架和设计思路(单例模式、依赖注入、gRPC 服务定义),遇到编译错误或测试失败时,将错误信息反馈给 AI,由 AI 分析原因并提出修改建议

**3. AI 解答的优点**
- 逐行解释了 Protobuf 消息结构、`oneof` 的使用方式、`Timestamp` 类型转换等
- 在 T3.1 测试失败后,通过分析错误堆栈,指出 `GetAnalysisResult` 中 `Failed` 分支的状态应为 `Success` 而非 `Failure`(获取结果操作本身是成功的,只是分析结果状态为 `Failed`)

**4. AI 解答的缺点**
- `GrpcTypeConverter.ConvertFromGrpc` 中新增 `Request` 和 `Internal` 分支时,AI 最初没有正确处理 `EventType` 字段的转换,需要我补充

**5. 从 AI 学到的新知识**
- Protobuf 的 `oneof` 类型:同一个消息中只能设置一个字段,通过 `EntryCase` 判断具体是哪种类型
- gRPC 流式调用:`ReadAllAsync()` 逐条读取服务端返回的消息序列

**6. 本节难度评价**
个人认为本节**难度偏高**。
- 调试时需要在两个终端之间切换,错误信息可能来自任何一方
- 类型转换(C# ↔ Protobuf)容易出现不匹配问题
64 changes: 64 additions & 0 deletions docs/04-avalonia/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
### 任务截图
![alt text](<屏幕截图 2026-08-31 093652-1.png>)
![alt text](<屏幕截图 2026-08-31 093801-1.png>)
![alt text](<屏幕截图 2026-08-31 093828.png>)

### Q4.1:GUI 应用程序与控制台应用程序的开发区别

#### 一、开发体验的区别

控制台程序都是线性执行的:程序从 `Main` 方法开始,按顺序执行,用户输入什么程序就做什么,做完就结束。这种模式很直观,调试只要看输出和错误信息就行。

而 GUI 应用程序完全不同。它启动后不会主动结束,而是等待用户操作(点击按钮、输入文本、选择文件等)。每次用户操作都会触发一个事件(Event),程序需要响应这个事件并执行相应的逻辑。这意味着:程序是事件驱动的,代码不会主动运行,而是“等待被调用”;程序是有状态的,连接状态、目录路径等都需要在内存中持续保存,并在界面中实时更新;要考虑用户体验,程序不能因为用户的一个错误操作就崩溃,也不能因为网络请求慢就让界面“卡死”。

#### 二、GUI 开发的主要难点

**1. MVVM 模式**
GUI 开发要求使用 **MVVM(Model-View-ViewModel)** 模式,把界面(View)、数据(Model)和逻辑(ViewModel)严格分离。
- **View**(界面层):只负责显示,不包含任何业务逻辑。在 Avalonia 中,`.axaml` 文件就是 View。
- **ViewModel**(逻辑层):负责处理用户操作、调用 gRPC、更新数据。它不知道界面长什么样,只提供数据和命令。
- **Model**(数据层):纯粹的数据容器,如 `LogFileItem`、`LogFields`。

**2. UI 线程阻塞问题**


在 GUI 程序中,所有界面渲染和用户交互都由 UI 线程负责。如果我在 UI 线程中执行耗时操作(比如同步调用 gRPC),界面就会“卡死”,用户无法点击任何按钮,甚至无法拖动窗口,体验极差。

因此, gRPC 调用必须使用异步方法(`client.XXXAsync` + `await`),在等待网络响应时 UI 线程可以继续处理其他事件(比如用户点击“取消”按钮)。这让我对 `async/await` 有了更深入的理解:

`await` 不会阻塞线程,而是“让出”线程,等任务完成后再回到 UI 线程继续执行。 如果不小心在 UI 线程上使用了 `.Result` 或 `.Wait()`,就会导致死锁(Deadlock),程序永远卡死。

**3. 数据绑定(Binding)**

Avalonia 中,ViewModel 的数据变化要自动反映到界面上,需要使用数据绑定。
- `ObservableCollection<T>`:当集合内容变化时(增加、删除、清空),界面会自动更新。
- `[ObservableProperty]`:`CommunityToolkit.Mvvm` 自动生成属性变化通知代码,省去了手动实现 `INotifyPropertyChanged` 的麻烦。
- `[RelayCommand]`:把方法自动包装成命令,可以直接绑定到按钮的 `Command` 属性。


**4. 调试的复杂性**

GUI 的调试更加复杂需要:
- **同时启动两个进程**:Agent 和 GUI 客户端,需要在两个终端之间切换。
- **查看异步调用栈**:`await` 后的代码可能在另一个线程上执行,调试时需要注意线程上下文。
- **处理事件绑定错误**:如果 `Command` 绑定的方法名写错了(比如 `AnalyzeAllCommand` 拼成 `AnalyzeAllCommnad`),编译不会报错,但按钮点击后没有反应,很难发现。

#### 三、对 async/await 的进一步理解

通过这次 GUI 开发,我对异步编程有了更深刻的理解:

1. **异步是“非阻塞”的,不是“并行”的**:`await` 只是让出当前线程,让 UI 线程可以继续处理事件,而不是真的在并行执行。真正的并行需要 `Task.Run` 或 `Parallel.ForEach`。

2. **`async void` 要谨慎使用**:在事件处理中,`async void` 可以用于“触发即忘”的场景,但如果 `async void` 方法中抛出异常,程序会直接崩溃。因此我尽量使用 `async Task`,让调用者可以 `await`。


#### 四、异步编程带来的困扰

异步编程确实带来了一些额外的复杂性:

1. **调试更困难**:`await` 后的代码可能在另一个线程上执行,断点跳转顺序不如同步代码直观。
2. **错误堆栈不完整**:有时异常抛出的位置和捕获的位置相距较远,需要查看 `InnerException` 才能定位。
3. **`async` 会“传染”**:一旦一个方法用了 `async`,所有调用它的方法也要变成 `async`,否则就要用 `.Result`(容易死锁)。

### Q4.2.b
我的提示词主要是提供 guidance 和代码文件,要求 AI 逐行解释框架并指导 TODO 实现。AI 帮助我快理解了 MVVM 模式、异步 UI 编程和 Avalonia 的数据绑定机制。不过在项目结构上,AI 曾误判 LogAnalyzerClient.Desktop 为独立文件夹,导致运行命令出错。AI帮我更快掌握了 ObservableCollection、[RelayCommand] 和流式 gRPC 调用的使用,同时对 async/await 在 GUI 中的作用有了更深的理解。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 63 additions & 4 deletions src/LocalCli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,81 @@

private static void ShowLogFiles(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
var files = analyzer.GetLogFiles(); // 问分析器要文件列表
if (files.Count == 0)
{
Console.WriteLine("目录下没有 .log 文件。");
return;
}
Console.WriteLine("文件列表:");
foreach (var file in files) // 遍历输出
{
Console.WriteLine($" - {file}");
}
}

private static void AnalyzeFiles(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
Console.Write("请输入要分析的文件名(多个用逗号分隔): ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input)) return; // 如果输入空,直接返回

var names = input.Split(',')
.Select(n => n.Trim())
.Where(n => !string.IsNullOrEmpty(n))
.ToList();

try
{
analyzer.AnalyzeFiles(0, names); // 0 表示自动用 CPU 核心数
Console.WriteLine("分析任务已执行完毕。");
}
catch (Exception ex)
{
Console.WriteLine($"分析过程中发生错误: {ex.Message}");
}
}

private static void AnalyzeAll(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
try
{
analyzer.AnalyzeAll(0);
Console.WriteLine("全部分析任务已执行完毕。");
}
catch (Exception ex) { /* 抓异常防止崩溃 */ }

Check warning on line 157 in src/LocalCli/Program.cs

View workflow job for this annotation

GitHub Actions / test-04-avalonia

The variable 'ex' is declared but never used
}

private static void GetAnalysisResult(LogFileAnalyzer analyzer)
{
throw new NotImplementedException("T2.3");
Console.Write("请输入要查看的文件名: ");
string? fileName = Console.ReadLine();
if (string.IsNullOrWhiteSpace(fileName)) return;

if (analyzer.TryGetAnalysisResult(fileName, out var result))
{
switch (result.State)

Check warning on line 168 in src/LocalCli/Program.cs

View workflow job for this annotation

GitHub Actions / test-04-avalonia

Dereference of a possibly null reference.
{
case AnalysisState.NotAnalyzed:
Console.WriteLine($"文件 {fileName} 尚未分析。");
break;
case AnalysisState.Succeeded:
var visitor = new KeyValueVisitor();
foreach (var entry in result.Entries)
{
var dict = visitor.Dump(entry);
Console.WriteLine(string.Join(", ", dict.Select(kv => $"{kv.Key}={kv.Value}")));
}
break;
case AnalysisState.Failed:
Console.WriteLine($"文件 {fileName} 分析失败: {result.ErrorMessage}");
break;
}
}
else
{
Console.WriteLine($"未找到文件 {fileName} 的分析结果。");
}
}
}
}
Loading
Loading