异步异常处理:AggregateException 的拆解与最佳实践

你是否遇到过这样的场景:

  • 为什么 await 抛出的是单个异常,而 .Result 抛出的是 AggregateException
  • 同时调用 10 个 API,其中 3 个失败了,如何获取所有失败信息?
  • Task.WhenAll 抛异常时,为什么只能捕获第一个?
  • 后台任务(Fire-and-forget)的异常去哪儿了?
  • 如何实现一个"容错"的并发任务执行器?

今天,我们就来彻底搞懂 .NET 异步编程中的异常处理机制 ,从 AggregateException 的设计理念到实战技巧,一网打尽。


0️⃣ 一个真实的故事:消失的异常

0.1 场景重现:批量调用 API

假设你正在写一个数据同步工具,需要同时调用 10 个微服务的 API,获取数据并汇总。

你写出了第一版代码:

csharp 复制代码
public async Task<List<UserData>> GetAllUsersAsync()
{
    var tasks = new List<Task<UserData>>();

    // 并发调用 10 个 API
    for (int i = 1; i <= 10; i++)
    {
        tasks.Add(GetUserDataAsync(i));
    }

    // 等待所有任务完成
    var results = await Task.WhenAll(tasks);

    return results.ToList();
}

private async Task<UserData> GetUserDataAsync(int userId)
{
    using var client = new HttpClient();
    var response = await client.GetStringAsync($"https://api.example.com/users/{userId}");
    return JsonSerializer.Deserialize<UserData>(response);
}

测试一下:前面几次运行都正常,但突然有一天:

less 复制代码
未处理的异常: System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).

问题来了

  1. 只看到了一个异常,但实际上可能有多个 API 都失败了
  2. 其他成功的 API 数据丢失了
  3. 如何记录所有失败的 API,方便排查问题?

这就是我们今天要解决的核心问题。


0.2 新手的常见尝试

❌ 尝试 1:直接 try-catch(丢失了多个异常)

csharp 复制代码
try
{
    var results = await Task.WhenAll(tasks);
}
catch (Exception ex)
{
    Console.WriteLine($"出错了: {ex.Message}");
    // ❌ 只能捕获第一个异常!其他失败的任务信息丢失
}

结果:只捕获到了第一个异常,其他失败的任务信息全部丢失。

❌ 尝试 2:改用 .Result(引入了 AggregateException)

csharp 复制代码
try
{
    var whenAllTask = Task.WhenAll(tasks);
    whenAllTask.Wait(); // 或者 .Result
}
catch (AggregateException aggEx)
{
    foreach (var ex in aggEx.InnerExceptions)
    {
        Console.WriteLine($"出错了: {ex.Message}");
    }
    // ✅ 可以获取所有异常,但 Wait() 会阻塞线程!
}

结果 :能获取所有异常了,但 .Wait() 会阻塞线程,而且可能导致死锁(回顾第 05 章)。

✅ 正确的做法:await + 手动检查 Task.Exception

csharp 复制代码
var whenAllTask = Task.WhenAll(tasks);

try
{
    await whenAllTask;
}
catch (Exception firstEx)
{
    // 捕获第一个异常
    Console.WriteLine($"第一个异常: {firstEx.Message}");

    // 如果需要所有异常,从 Task.Exception 中获取
    if (whenAllTask.Exception != null)
    {
        Console.WriteLine("\n所有异常:");
        foreach (var ex in whenAllTask.Exception.InnerExceptions)
        {
            Console.WriteLine($"- {ex.Message}");
        }
    }
}

疑问 :为什么 await 只抛出第一个异常,而 .Wait() 抛出 AggregateException?这就要从 AggregateException 的设计思想说起。


1️⃣ AggregateException:为什么需要它?

1.1 单个异常 vs 多个异常

在传统的同步编程中,一个方法只会抛出一个异常

scss 复制代码
public void ProcessData()
{
    ValidateInput();    // 可能抛出 ArgumentException
    ConnectDatabase();  // 可能抛出 SqlException
    SaveData();         // 可能抛出 IOException
    // ❌ 一旦抛出异常,后面的代码不会执行
}

但在并发编程中,情况完全不同:

javascript 复制代码
var task1 = Task.Run(() => throw new InvalidOperationException("Task 1 failed"));
var task2 = Task.Run(() => throw new ArgumentException("Task 2 failed"));
var task3 = Task.Run(() => throw new IOException("Task 3 failed"));

await Task.WhenAll(task1, task2, task3);
// ❓ 三个任务都失败了,应该抛出哪个异常?

问题

  • 三个任务同时执行,都失败了
  • 传统的异常机制只能抛出一个异常
  • 如果只抛出第一个,其他两个异常信息就丢失了

解决方案AggregateException------一个可以包含多个异常的容器。


1.2 AggregateException 的设计结构

csharp 复制代码
public class AggregateException : Exception
{
    // 存储所有内部异常
    public ReadOnlyCollection<Exception> InnerExceptions { get; }

    // 扁平化嵌套的 AggregateException
    public AggregateException Flatten();

    // 按条件处理异常
    public void Handle(Func<Exception, bool> predicate);
}

核心特性

  1. InnerExceptions:存储所有子任务的异常
  2. Flatten() :处理嵌套的 AggregateException
  3. Handle() :选择性处理某些异常,未处理的会重新抛出

示例

csharp 复制代码
try
{
    var task1 = Task.Run(() => throw new InvalidOperationException("Task 1 failed"));
    var task2 = Task.Run(() => throw new ArgumentException("Task 2 failed"));
    var task3 = Task.Run(() => throw new IOException("Task 3 failed"));

    Task.WaitAll(task1, task2, task3); // ❌ 同步等待,会抛出 AggregateException
}
catch (AggregateException aggEx)
{
    Console.WriteLine($"捕获了 {aggEx.InnerExceptions.Count} 个异常:");

    foreach (var ex in aggEx.InnerExceptions)
    {
        Console.WriteLine($"- {ex.GetType().Name}: {ex.Message}");
    }
}

输出

arduino 复制代码
捕获了 3 个异常:
- InvalidOperationException: Task 1 failed
- ArgumentException: Task 2 failed
- IOException: Task 3 failed

1.3 await vs Wait/Result 的异常行为差异

这是一个非常重要的知识点,很多开发者在这里踩坑。

场景:单个任务失败

javascript 复制代码
var task = Task.Run(() => throw new InvalidOperationException("Something went wrong"));

方式 1:使用 await(推荐)

csharp 复制代码
try
{
    await task;
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"捕获异常: {ex.Message}");
    // ✅ 直接抛出原始异常,简化异常处理
}

方式 2:使用 Wait() 或 .Result

csharp 复制代码
try
{
    task.Wait(); // 或者 var result = task.Result;
}
catch (AggregateException aggEx)
{
    // ❌ 包装在 AggregateException 中,需要额外解包
    var innerEx = aggEx.InnerException;
    Console.WriteLine($"捕获异常: {innerEx.Message}");
}

对比表格

特性 await .Wait() / .Result
抛出的异常类型 原始异常(第一个) AggregateException
获取原始异常 直接捕获 aggEx.InnerException
多个异常 只抛出第一个 InnerExceptions 包含所有
线程阻塞 ❌ 不阻塞 ✅ 阻塞当前线程
死锁风险 ✅ 安全 ❌ 可能死锁(UI 线程)
推荐使用 强烈推荐 ❌ 尽量避免

结论

  • 优先使用 await:代码更简洁,异常处理更直观
  • 需要所有异常 :通过 Task.Exception 属性获取 AggregateException
  • 避免使用 .Wait().Result:会阻塞线程,可能导致死锁

2️⃣ Task.WhenAll 的异常陷阱与解决方案

2.1 问题:WhenAll 只抛出第一个异常

这是 Task.WhenAll 最容易踩坑的地方。

示例

scss 复制代码
public async Task CallMultipleApisAsync()
{
    var tasks = new[]
    {
        CallApiAsync(1), // ✅ 成功
        CallApiAsync(2), // ❌ 失败:404 Not Found
        CallApiAsync(3), // ✅ 成功
        CallApiAsync(4), // ❌ 失败:500 Internal Server Error
        CallApiAsync(5), // ❌ 失败:Timeout
    };

    try
    {
        await Task.WhenAll(tasks);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"捕获异常: {ex.Message}");
        // ❌ 只能看到第一个失败的异常(404 Not Found)
        // ❌ 其他两个失败(500 和 Timeout)的信息丢失
    }
}

输出

less 复制代码
捕获异常: Response status code does not indicate success: 404 (Not Found).

问题:只看到了第一个异常,其他失败信息丢失了!


2.2 解决方案 1:手动检查 Task.Exception

csharp 复制代码
public async Task CallMultipleApisAsync()
{
    var tasks = new[]
    {
        CallApiAsync(1),
        CallApiAsync(2),
        CallApiAsync(3),
        CallApiAsync(4),
        CallApiAsync(5),
    };

    var whenAllTask = Task.WhenAll(tasks);

    try
    {
        await whenAllTask;
    }
    catch (Exception firstEx)
    {
        Console.WriteLine($"第一个异常: {firstEx.Message}");

        // ✅ 从 Task.Exception 获取所有异常
        if (whenAllTask.Exception != null)
        {
            Console.WriteLine($"\n总共 {whenAllTask.Exception.InnerExceptions.Count} 个任务失败:");

            foreach (var ex in whenAllTask.Exception.InnerExceptions)
            {
                Console.WriteLine($"- {ex.GetType().Name}: {ex.Message}");
            }
        }
    }
}

输出

vbscript 复制代码
第一个异常: Response status code does not indicate success: 404 (Not Found).

总共 3 个任务失败:
- HttpRequestException: Response status code does not indicate success: 404 (Not Found).
- HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).
- TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 30 seconds elapsing.

优点:✅ 可以获取所有异常信息

缺点:❌ 代码略显冗长


2.3 解决方案 2:逐个 await(更简洁)

scss 复制代码
public async Task CallMultipleApisAsync()
{
    var tasks = new[]
    {
        CallApiAsync(1),
        CallApiAsync(2),
        CallApiAsync(3),
        CallApiAsync(4),
        CallApiAsync(5),
    };

    // 先启动所有任务(并发执行)
    var whenAllTask = Task.WhenAll(tasks);

    // 逐个 await,捕获每个任务的异常
    foreach (var task in tasks)
    {
        try
        {
            await task;
            Console.WriteLine("✅ 任务成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"❌ 任务失败: {ex.Message}");
        }
    }
}

输出

vbscript 复制代码
✅ 任务成功
❌ 任务失败: Response status code does not indicate success: 404 (Not Found).
✅ 任务成功
❌ 任务失败: Response status code does not indicate success: 500 (Internal Server Error).
❌ 任务失败: The request was canceled due to the configured HttpClient.Timeout of 30 seconds elapsing.

优点

  • ✅ 可以单独处理每个任务的异常
  • ✅ 代码清晰,逻辑直观

注意

  • Task.WhenAll 仍然需要调用,确保所有任务并发执行
  • 逐个 await 时,已完成的任务会立即返回,不会重新执行

2.4 解决方案 3:实现 SafeWhenAll 扩展方法(最优雅)

目标:封装异常处理逻辑,返回成功和失败的结果。

csharp 复制代码
public static async Task<(List<T> Successes, List<Exception> Failures)> SafeWhenAll<T>(
    this IEnumerable<Task<T>> tasks)
{
    var taskList = tasks.ToList();
    var successes = new List<T>();
    var failures = new List<Exception>();

    foreach (var task in taskList)
    {
        try
        {
            var result = await task;
            successes.Add(result);
        }
        catch (Exception ex)
        {
            failures.Add(ex);
        }
    }

    return (successes, failures);
}

使用示例

scss 复制代码
public async Task CallMultipleApisAsync()
{
    var tasks = new[]
    {
        CallApiAsync(1),
        CallApiAsync(2),
        CallApiAsync(3),
        CallApiAsync(4),
        CallApiAsync(5),
    };

    var (successes, failures) = await tasks.SafeWhenAll();

    Console.WriteLine($"✅ 成功: {successes.Count} 个");
    Console.WriteLine($"❌ 失败: {failures.Count} 个");

    if (failures.Any())
    {
        Console.WriteLine("\n失败详情:");
        foreach (var ex in failures)
        {
            Console.WriteLine($"- {ex.GetType().Name}: {ex.Message}");
        }
    }
}

输出

vbscript 复制代码
✅ 成功: 2 个
❌ 失败: 3 个

失败详情:
- HttpRequestException: Response status code does not indicate success: 404 (Not Found).
- HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).
- TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 30 seconds elapsing.

优点

  • ✅ 封装良好,可复用
  • ✅ 同时获取成功和失败的结果
  • ✅ 不丢失任何异常信息

2.5 实战场景:并发调用 API + 容错处理

需求

  • 同时调用 10 个 API
  • 允许部分失败,只要有 5 个成功就算整体成功
  • 记录所有失败的 API,方便排查

实现

csharp 复制代码
public class ApiAggregator
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<ApiAggregator> _logger;

    public ApiAggregator(HttpClient httpClient, ILogger<ApiAggregator> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }

    public async Task<AggregatedResult> GetAggregatedDataAsync(
        IEnumerable<string> apiUrls,
        int minSuccessCount = 5,
        CancellationToken cancellationToken = default)
    {
        var tasks = apiUrls.Select(url => CallApiWithLoggingAsync(url, cancellationToken)).ToList();

        var (successes, failures) = await tasks.SafeWhenAll();

        // 记录失败信息
        foreach (var ex in failures)
        {
            _logger.LogError(ex, "API 调用失败");
        }

        // 检查是否满足最低成功数量
        if (successes.Count < minSuccessCount)
        {
            throw new InvalidOperationException(
                $"API 调用失败过多:期望至少 {minSuccessCount} 个成功,实际只有 {successes.Count} 个成功");
        }

        return new AggregatedResult
        {
            Successes = successes,
            FailureCount = failures.Count,
            Errors = failures.Select(ex => ex.Message).ToList()
        };
    }

    private async Task<ApiResponse> CallApiWithLoggingAsync(string url, CancellationToken cancellationToken)
    {
        try
        {
            _logger.LogInformation("开始调用 API: {Url}", url);

            var response = await _httpClient.GetStringAsync(url, cancellationToken);
            var data = JsonSerializer.Deserialize<ApiResponse>(response);

            _logger.LogInformation("API 调用成功: {Url}", url);

            return data;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "API 调用失败: {Url}", url);
            throw; // 重新抛出,由 SafeWhenAll 捕获
        }
    }
}

public class AggregatedResult
{
    public List<ApiResponse> Successes { get; set; }
    public int FailureCount { get; set; }
    public List<string> Errors { get; set; }
}

使用示例

csharp 复制代码
var apiUrls = new[]
{
    "https://api1.example.com/data",
    "https://api2.example.com/data",
    "https://api3.example.com/data",
    // ... 更多 API
};

try
{
    var result = await aggregator.GetAggregatedDataAsync(apiUrls, minSuccessCount: 5);

    Console.WriteLine($"✅ 成功获取 {result.Successes.Count} 个数据");
    Console.WriteLine($"❌ 失败 {result.FailureCount} 个请求");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"❌ {ex.Message}");
}

3️⃣ 后台任务(Fire-and-Forget)的异常处理

3.1 问题:后台任务的异常会被吞掉

场景:启动一个后台任务,不等待它完成。

csharp 复制代码
// ❌ 错误示例:异常会被吞掉
public void StartBackgroundTask()
{
    _ = DoWorkAsync(); // Fire-and-forget
}

private async Task DoWorkAsync()
{
    await Task.Delay(1000);
    throw new InvalidOperationException("后台任务失败了!");
    // ❌ 这个异常不会被捕获,程序不会崩溃,但异常信息丢失
}

问题

  • 异常被吞掉,无法排查问题
  • 可能导致未处理的异常(UnobservedTaskException)
  • 资源可能没有正确释放

3.2 解决方案 1:使用 TaskScheduler.UnobservedTaskException

javascript 复制代码
// 在程序启动时注册全局异常处理器
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
    Console.WriteLine($"❌ 未观察到的异常: {e.Exception.Message}");

    // 标记为已观察,防止程序崩溃
    e.SetObserved();
};

缺点

  • 只在垃圾回收时触发,可能延迟很久
  • .NET Core 默认不会导致程序崩溃,容易忽略异常

3.2 解决方案 2:SafeFireAndForget 扩展方法(推荐)

csharp 复制代码
public static async void SafeFireAndForget(
    this Task task,
    Action<Exception> onException = null)
{
    try
    {
        await task;
    }
    catch (Exception ex)
    {
        // 调用自定义异常处理器
        onException?.Invoke(ex);

        // 或者记录日志
        Console.WriteLine($"❌ 后台任务异常: {ex.Message}");
    }
}

使用示例

csharp 复制代码
public void StartBackgroundTask()
{
    DoWorkAsync().SafeFireAndForget(ex =>
    {
        _logger.LogError(ex, "后台任务失败");
        // 可以发送告警、记录到数据库等
    });
}

private async Task DoWorkAsync()
{
    await Task.Delay(1000);
    throw new InvalidOperationException("后台任务失败了!");
}

优点

  • ✅ 异常不会被吞掉
  • ✅ 可以自定义异常处理逻辑
  • ✅ 代码清晰,意图明确

3.3 解决方案 3:使用 BackgroundService(.NET Core)

如果是长期运行的后台任务,推荐使用 IHostedServiceBackgroundService

csharp 复制代码
public class MyBackgroundService : BackgroundService
{
    private readonly ILogger<MyBackgroundService> _logger;

    public MyBackgroundService(ILogger<MyBackgroundService> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await DoWorkAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                // ✅ 异常会被记录,服务继续运行
                _logger.LogError(ex, "后台任务执行失败");

                // 等待一段时间后重试
                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
    }

    private async Task DoWorkAsync(CancellationToken cancellationToken)
    {
        // 业务逻辑
        await Task.Delay(5000, cancellationToken);
    }
}

4️⃣ AggregateException 的高级用法

4.1 Flatten():扁平化嵌套异常

问题 :嵌套的 Task.WhenAll 会产生嵌套的 AggregateException

csharp 复制代码
var task1 = Task.Run(() => throw new InvalidOperationException("Task 1"));
var task2 = Task.Run(() => throw new ArgumentException("Task 2"));

var outerTask = Task.Run(() =>
{
    Task.WaitAll(task1, task2); // 内层 AggregateException
});

try
{
    outerTask.Wait(); // 外层 AggregateException
}
catch (AggregateException aggEx)
{
    // aggEx.InnerExceptions[0] 是另一个 AggregateException
    // 需要递归处理

    // ✅ 使用 Flatten() 扁平化
    var flattenedEx = aggEx.Flatten();

    foreach (var ex in flattenedEx.InnerExceptions)
    {
        Console.WriteLine($"- {ex.GetType().Name}: {ex.Message}");
    }
}

输出

arduino 复制代码
- InvalidOperationException: Task 1
- ArgumentException: Task 2

4.2 Handle():选择性处理异常

场景:某些异常可以忽略,某些异常需要重新抛出。

kotlin 复制代码
try
{
    Task.WaitAll(tasks);
}
catch (AggregateException aggEx)
{
    aggEx.Handle(ex =>
    {
        // 如果是 TaskCanceledException,忽略它
        if (ex is TaskCanceledException)
        {
            Console.WriteLine("任务被取消,忽略");
            return true; // 标记为已处理
        }

        // 其他异常不处理,会重新抛出
        return false;
    });
}

行为

  • Handle() 返回 true:异常被处理,不会重新抛出
  • Handle() 返回 false:异常未处理,会重新抛出

5️⃣ 异常处理的最佳实践

5.1 优先使用 await + try-catch

csharp 复制代码
// ✅ 推荐:简洁清晰
try
{
    var result = await SomeOperationAsync();
}
catch (HttpRequestException ex)
{
    // 处理网络异常
}
catch (JsonException ex)
{
    // 处理 JSON 解析异常
}
csharp 复制代码
// ❌ 不推荐:引入 AggregateException,增加复杂度
try
{
    var result = SomeOperationAsync().Result;
}
catch (AggregateException aggEx)
{
    foreach (var ex in aggEx.InnerExceptions)
    {
        // ...
    }
}

5.2 Task.WhenAll 需要所有异常时

php 复制代码
var whenAllTask = Task.WhenAll(tasks);

try
{
    await whenAllTask;
}
catch
{
    // 从 Task.Exception 获取所有异常
    if (whenAllTask.Exception != null)
    {
        foreach (var ex in whenAllTask.Exception.InnerExceptions)
        {
            _logger.LogError(ex, "任务失败");
        }
    }
}

5.3 后台任务必须有异常处理

ini 复制代码
// ❌ 错误:异常会被吞掉
_ = DoWorkAsync();

// ✅ 正确:使用 SafeFireAndForget
DoWorkAsync().SafeFireAndForget(ex =>
{
    _logger.LogError(ex, "后台任务失败");
});

5.4 库代码不要吞掉异常

csharp 复制代码
// ❌ 错误:吞掉异常,调用者无法感知
public async Task<Result> TryGetDataAsync()
{
    try
    {
        return await GetDataAsync();
    }
    catch
    {
        return null; // 吞掉异常
    }
}

// ✅ 正确:让异常传播,或返回明确的错误状态
public async Task<Result<T, Error>> TryGetDataAsync()
{
    try
    {
        var data = await GetDataAsync();
        return Result.Success(data);
    }
    catch (Exception ex)
    {
        return Result.Failure(ex.Message);
    }
}

5.5 异常信息要足够详细

csharp 复制代码
// ❌ 错误:异常信息不明确
throw new Exception("出错了");

// ✅ 正确:提供上下文信息
throw new InvalidOperationException(
    $"API 调用失败: URL={url}, StatusCode={statusCode}, ErrorMessage={errorMessage}");

6️⃣ 实战总结:异常处理清单

✅ Do's(应该做的)

场景 推荐做法
单个任务 使用 await + try-catch
多个任务(需要所有异常) await Task.WhenAll + 检查 Task.Exception
多个任务(容错) 使用 SafeWhenAll 扩展方法
后台任务 使用 SafeFireAndForgetBackgroundService
嵌套异常 使用 Flatten() 扁平化
选择性处理 使用 Handle() 方法
记录日志 catch 块中使用 ILogger

❌ Don'ts(不应该做的)

场景 问题
使用 .Wait().Result 阻塞线程,可能死锁
吞掉异常(空 catch) 隐藏问题,难以排查
忽略后台任务异常 资源泄漏,问题难以发现
只捕获第一个异常 丢失其他失败信息
异常信息不足 难以定位问题

7️⃣ 本章小结

核心知识点

  1. AggregateException 的设计思想

    • 为并发任务设计的异常容器
    • 可以包含多个子异常
    • 提供 Flatten()Handle() 高级功能
  2. await vs Wait/Result 的异常行为

    • await:抛出第一个原始异常,简化处理
    • .Wait() / .Result:抛出 AggregateException,阻塞线程
    • 优先使用 await
  3. Task.WhenAll 的异常处理

    • await 只抛出第一个异常
    • 通过 Task.Exception 获取所有异常
    • 使用 SafeWhenAll 封装处理逻辑
  4. 后台任务的异常处理

    • 异常容易被吞掉
    • 使用 SafeFireAndForgetBackgroundService
    • 注册全局的 UnobservedTaskException 处理器

进阶思考

之前去面试,碰见了一道面试题,至今记忆犹新。现在拿出来,供大家思考一下:

有一组API,数量记为N,50<=N<=500。现在要设计一个功能,要求在最短的时间内找出这组API里成功响应时间最短的API,每次最大并发请求数量限制为10。

相关推荐
柒和远方1 小时前
每日一学V017:用 Prompt 做 NLP:解构赋值与 AI 全栈的第一次实战
javascript·架构·代码规范
砍材农夫2 小时前
物联网实战:Spring Boot MQTT | 模拟器Paho客户端拆解高性能
java·javascript·spring boot·后端·物联网·struts
代码小库2 小时前
【2026前端最新面试题——day10】JavaScript 高频面试题
开发语言·前端·javascript
零陵上将军_xdr2 小时前
后端转全栈学习-Day4-JavaScript 基础-2
开发语言·javascript·学习
whatever who cares3 小时前
Vue3中vue文件和composables的分工
前端·javascript·vue.js
十正3 小时前
Claude code源码精读之蜂群模式
javascript·人工智能·agent·claude code
薛先生_0993 小时前
vue-编程式跳转-基本跳转
前端·javascript·vue.js
AI_零食4 小时前
健身室器材管理系统鸿蒙PC Electron框架编写深度解析
前端·javascript·学习·华为·electron·前端框架·鸿蒙
如烟花的信页4 小时前
易盾点选逆向分析
javascript·爬虫·python·js逆向