# 调试异步代码

> For the complete documentation index, see [llms.txt](https://www.jetbrains.com/zh-cn/help/idea/llms.txt).

> **TL;DR**
> 启用/禁用：  `设置 | 构建、执行、部署 | 调试器 | 异步堆栈跟踪 | Instrumenting agent`

调试异步代码是一项挑战，因为任务通常在一个线程中调度，而在另一个线程中执行。 每个线程都有自己的堆栈跟踪，这使得很难弄清楚线程启动之前发生了什么。

IntelliJ IDEA 通过在不同线程中的帧之间建立连接来简化操作。 这使您能够从一个工作线程回溯到任务被调度的位置，并像在同一线程中执行一样调试程序。

要尝试异步堆栈跟踪，请调试以下示例：

```JAVA
import java.util.*;
import java.util.concurrent.*;

public class AsyncExample {

    static List<Task> tasks = new ArrayList<>();
    static ExecutorService executor = Executors.newScheduledThreadPool(4);

    public static void main(String[] args) {
        createTasks();
        executeTasks();
    }

    private static void createTasks() {
        for (int i = 0; i < 20; i++) {
            tasks.add(new Task(i));
        }
    }

    private static void executeTasks() {
        for (Task task : tasks) {
            executor.submit(task);
        }
    }

    static class Task extends Thread {

        int num;

        public void run() {
            try {
                Thread.sleep(new Random().nextInt(2000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            printNum();
        }

        private void printNum() {
            // Set a breakpoint at the following line
            System.out.print(num + " ");
        }

        public Task(int num) {
            this.num = num;
        }
    }
}

```

当我们在  `printNum()`  方法中的断点处停止时，我们有两个可用的堆栈跟踪：

* 当前线程（worker）

* 主线程（任务被调度的地方）

![线程选项卡显示堆栈跟踪的两个部分——一个是工作线程的，另一个是调度线程的](https://resources.jetbrains.com/help/img/idea/2026.3/async_stacktraces.png)

## 控制台中的异步堆栈跟踪

默认情况下，当您调试代码或运行 [JUnit](junit.html)/ [TestNG](testng.html) 单元测试时，异步堆栈跟踪会显示在控制台中。

![在控制台中打印的失败测试的异步堆栈跟踪](https://resources.jetbrains.com/help/img/idea/2026.3/async-stack-trace-in-console.png)

您可以通过清除相应运行配置中的   打印异常的异步堆栈跟踪   复选框来禁用测试的异步堆栈跟踪。

![运行配置对话框中的“打印异常的异步堆栈跟踪”复选框](https://resources.jetbrains.com/help/img/idea/2026.3/async-stack-trace-in-console-remove.png)

要为调试会话启用或禁用异步堆栈跟踪，请使用   插装代理   选项，在  `设置 | 构建、执行、部署 | 调试器 | 异步堆栈跟踪`  中进行设置。

当命中启用了   堆栈跟踪   [日志记录选项](using-breakpoints.html#log) 的断点时，异步堆栈跟踪也会显示在控制台中。

## 异步注解

异步堆栈跟踪开箱即用地支持 Swing 和 Java 并发 API，但也可以手动扩展以支持您自己的自定义类。 这是通过使用特殊注释完成的。

> **Note:**
> 为了使用异步注解，您需要 [将注解库包含到您的项目中](annotating-source-code.html)。 如果添加依赖项不可行，您可以 [创建自己的注释](#custom_async_annotations)。

注解用于定义捕获和插入点：

* 捕获点 是一种捕获堆栈跟踪的方法。 在 capture points，堆栈跟踪会被存储并分配一个键。 捕获点用 `@Async.Schedule` 注释标记。

* 插入点 是一种方法，将以前存储的堆栈跟踪之一附加到当前堆栈。 插入点用 `@Async.Execute` 注释标记。

* 一个 key 是一个参数或对象引用，用作捕获的堆栈跟踪的唯一标识符。 它用于匹配异步堆栈跟踪的各部分。

Procedure: 定义捕获和插入点

您可以注释方法或它们的参数：

* 如果您希望将对象引用 (  `this` ) 用作键，请对方法本身进行注解，例如：

```JAVA
@Async.Schedule
private static void schedule(Integer i) {
    System.out.println("Scheduling " + i);
    queue.put(i);
}

```

* 如果您希望将参数值用作键，请注释方法参数，例如：

```JAVA
private static void schedule(@Async.Schedule Integer i) {
    System.out.println("Scheduling " + i);
    queue.put(i);
}

```

要测试注解的工作方式，您可以使用以下示例：

```JAVA
import org.jetbrains.annotations.Async;

import java.util.concurrent.*;

public class AsyncSchedulerExample {
    private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                while (true) {
                    process(queue.take());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        schedule(1);
        schedule(2);
        schedule(3);
    }

    private static void schedule(@Async.Schedule Integer i) throws InterruptedException {
        System.out.println("Scheduling " + i);
        queue.put(i);
    }

    private static void process(@Async.Execute Integer i) {
        // Set a breakpoint at the following line
        System.out.println("Processing " + i);
    }
}

```

## 定义自定义注解

如果不想为项目添加 [JetBrains 注解依赖项](annotating-source-code.html)，可以定义自己的注解并使用它们来代替默认注解。

Procedure:

1. 为捕获点和插入点创建您自己的注解。 请参阅 [Async.java](https://github.com/JetBrains/java-annotations/blob/master/src/jvmMain/java/org/jetbrains/annotations/Async.java) 和 [Async.kt](https://github.com/JetBrains/java-annotations/blob/master/src/commonMain/kotlin/org/jetbrains/annotations/Async.kt) 以供参考。

2. 按 `Ctrl+Alt+S` (Windows), `⌘ Comma` (macOS), `⌘ Comma` (IntelliJ IDEA Classic (macOS)), `⌘ Comma` (macOS System Shortcuts), `Ctrl+Alt+S` (XWin), `Ctrl+Alt+S` (GNOME), `Ctrl+Alt+S` (KDE), `Ctrl+Alt+S` (Emacs), `Ctrl+Alt+S` (Sublime Text), `⌘ Comma` (Sublime Text (macOS)), `Ctrl+Alt+S` (Visual Studio), `⌘ Comma` (Visual Studio (macOS)), `Ctrl+Alt+S` (NetBeans), `Ctrl+Alt+S` (Eclipse), `⌘ Comma` (Eclipse (macOS)) 打开设置，然后选择  `构建 | 执行 | 部署 | 调试器 | 异步堆栈跟踪` 。

3. 点击   配置注解  。

4. 在   异步注解配置  对话框中，点击 ![添加](https://resources.jetbrains.com/help/img/idea/2026.3/debugger-agent.jar 复制到远程机器上的任意位置

* 将 `-javaagent:<path to debugger-agent.jar>` 添加到远程 JVM 选项

## 异步堆栈跟踪的开销

在某些情况下，异步堆栈跟踪可能会带来明显的开销。 当异步堆栈过长时，例如包含大量  `CompletableFuture`  回调或协程延续，就可能发生这种情况。

出现这种情况时，IntelliJ IDEA 会进行限流，仅当异步堆栈跟踪性能正常时才收集。 由于限流而未收集的异步堆栈跟踪会在   帧   选项卡中以   无法捕获   标记。 可以在  `设置 | 构建、执行、部署 | 调试器 | 异步堆栈跟踪`  中控制此行为。

要测量和管理调试器开销，请参阅 [监控调试器开销](monitor-debugger-overhead.html)。

