PAYATHON 2026

C# await 后的 continuation 在哪个线程运行?如何保证数据安全?

数据小陈

结论

await 不会让后续代码自动变得线程安全,也不保证 continuation 总在固定线程上运行。多个 DoThing 调用可能并发执行。如果它们同时对同一个实例的计数器执行 ++,就需要加锁,或使用 Interlocked 等线程安全操作。

await 后的代码何时、在哪儿继续执行

async 方法会先在调用它的线程上同步运行,直到遇到一个尚未完成的 await。如果等待的 Task 已经完成,后续代码可能直接在当前线程继续运行;如果任务还未完成,方法会挂起,并在任务完成后安排 continuation。

默认情况下,await 会尝试捕获当前的 SynchronizationContext 或相关的任务调度器。例如,在有 UI SynchronizationContext 的应用中,continuation 通常会回到该上下文执行。没有这类上下文时,continuation 通常由默认任务调度器安排,常见情况下会在线程池线程上运行。使用 ConfigureAwait(false) 可以不捕获上下文。

因此,题目里的 A 和 B 只是可能出现的线程情况,DoThingAsync 不一定会创建线程 B。异步 I/O 通常不需要专门占用一个线程来等待;代码具体在哪儿继续执行,也取决于所等待任务的状态和当前上下文。

多次调用时为什么会有数据竞争

假设多次调用都使用同一个 ThingDoer 实例,而且各自的 DoThingAsync() 完成时间相近,那么多个 continuation 可能会同时运行。ThingsDone++ 实际包含读取、加一和写回几个步骤,并不是不可分割的操作。并发执行时,更新可能互相覆盖。

原示例声明的是 ThingsDone,递增处写的却是 thingsDone。C# 区分大小写,下面的示例统一使用 ThingsDone。

处理方式

可以用 lock 保护共享计数器的更新。如果其他线程也会读取计数器,而且读写之间需要保持一致,就让读取和更新都使用同一把锁:

class ThingDoer
{
    private readonly object _lock = new object();
    private int _thingsDone;

    public int ThingsDone
    {
        get
        {
            lock (_lock)
            {
                return _thingsDone;
            }
        }
    }

    public async Task DoThing()
    {
        await DoThingAsync();

        lock (_lock)
        {
            _thingsDone++;
        }
    }

    private Task DoThingAsync()
    {
        // 执行异步操作
        throw new NotImplementedException();
    }
}

如果只需对整数计数器进行原子递增,可以使用 Interlocked.Increment:

private int _thingsDone;

public int ThingsDone => Volatile.Read(ref _thingsDone);

public async Task DoThing()
{
    await DoThingAsync();
    Interlocked.Increment(ref _thingsDone);
}

Interlocked.Increment 能保证递增操作本身是原子的。如果要保护多个字段组成的状态,或更复杂的操作,应使用锁或其他合适的同步方案。

调用方注意事项

如果要并发启动多个操作,并在最后等待它们完成,可以保留每次调用返回的 Task,再使用 Task.WhenAll:

Task[] tasks = Enumerable.Range(0, count)
    .Select(_ => thingDoer.DoThing())
    .ToArray();

await Task.WhenAll(tasks);

异步操作通常应返回 Task,而不是使用 async void,这样调用方才能等待操作完成并观察异常。无论 continuation 最终在哪个线程运行,只要多个操作可能同时访问同一份可变状态,就应根据访问方式选择合适的同步策略。

备注:内容仅供参考。