You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用TaskCompletionSource实现循环Task的暂停与恢复

问题解答

你当前的暂停逻辑确实是通过取消Task实现的

你的判断完全正确,现有代码的暂停本质是终止正在运行的计数任务:

  • 点击暂停时直接调用tokenSource.Cancel()触发取消信号,countNumber循环内的ThrowIfCancellationRequested()会抛出OperationCanceledException,直接终止整个Task的执行
  • 点击恢复时是新建了一个CancellationTokenSource,从保存的_nCurrentCount值启动全新的计数Task,并不是唤醒之前被暂停的旧Task
  • 这套逻辑虽然能正常运行,但有潜在bug:取消后立刻调用tokenSource.Dispose(),如果此时Task.Delay还在监听该token,有概率触发对象已释放的异常。

无需取消Task的暂停实现方法

不需要销毁重建Task,用异步等待信号的方式就能实现Task的挂起和恢复,全程保持同一个Task运行,核心是用适配异步场景的SemaphoreSlim做暂停状态开关,CancellationToken仅用于真正需要终止计数的场景(比如用户主动取消、关闭窗口),不再承担暂停功能。

核心实现逻辑

  • 新增类级别的SemaphoreSlim实例作为暂停信号,初始计数设为1,代表非暂停放行状态
  • 计数循环每次迭代前先异步等待信号量:如果处于暂停状态,代码会停在等待处不往下执行,直到收到恢复信号,整个Task不会退出
  • 点击暂停/恢复按钮时,只需要切换信号量的计数状态即可,不需要触发Task取消

改造后代码参考

首先定义类级别字段:

// 原有字段保留
private int _nCurrentCount = 0;
private CancellationTokenSource _countCts;
// 新增暂停信号量,初始为放行状态
private readonly SemaphoreSlim _pauseSignal = new SemaphoreSlim(1, 1);
private bool _isPaused = false;

按钮点击事件改造:

private void btnStart_Click(object sender, EventArgs e)
{
    btnCancel.Enabled = true;

    if (_isPaused)
    {
        // 恢复逻辑:释放信号量,放行等待中的计数逻辑
        _pauseSignal.Release();
        _isPaused = false;
        btnStart.Text = "PAUSE";
    }
    else if (_countCts != null)
    {
        // 暂停逻辑:将信号量计数置0,循环走到等待处就会挂起
        _pauseSignal.Wait(0);
        _isPaused = true;
        btnStart.Text = "RESUME";
    }
    else
    {
        // 首次启动计数
        _countCts = new CancellationTokenSource();
        btnStart.Text = "PAUSE";
        _nCurrentCount = 0;

        countNumber(_nCurrentCount, _countCts.Token).ContinueWith((t) =>
        {
            // 计数结束后重置状态
            if (_isPaused)
            {
                _pauseSignal.Release();
                _isPaused = false;
            }
            _countCts.Dispose();
            _countCts = null;

            if (InvokeRequired)
            {
                Invoke((MethodInvoker)(() =>
                {
                    txtCount.Text = _nCurrentCount.ToString();
                    btnStart.Text = "START";
                    btnStart.Enabled = true;
                }));
            }
        }, TaskContinuationOptions.ExecuteSynchronously);
    }
}

计数方法改造:

private async Task countNumber(int pnStart, CancellationToken pCancellationToken)
{
    if (pnStart < 0) pnStart = 0;
    int targetCount = Convert.ToInt32(txtInput.Text);
    for (int i = pnStart; i <= targetCount; i++)
    {
        pCancellationToken.ThrowIfCancellationRequested();
        // 等待暂停信号,暂停状态下会在此异步等待直到恢复,Task不会终止
        await _pauseSignal.WaitAsync(pCancellationToken);
        _pauseSignal.Release(); // 放行后立刻释放信号,保证下次循环能正常判断状态

        _nCurrentCount = i;
        // 跨线程更新UI必须切回UI线程
        if (InvokeRequired)
        {
            Invoke((MethodInvoker)(() => txtCount.Text = _nCurrentCount.ToString()));
        }

        await Task.Delay(500, pCancellationToken);
    }
}

补充:原代码直接在异步Task中访问UI控件txtInput.Text、txtCount.Text属于线程不安全操作,改造后统一通过Invoke切回UI线程更新,避免随机抛出跨线程异常。

方案优势

  • 全程只有一个计数Task运行,暂停时只是异步等待信号,没有销毁、重建Task的额外开销
  • 职责划分清晰:CancellationToken只负责真正的终止操作,暂停/恢复完全由信号量控制
  • 规避了原实现中取消后立刻释放TokenSource导致的潜在异常

内容的提问来源于stack exchange,提问作者QuangGiap

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:51:34