C# Task的取消暂停

CancellationTokenSource 和 CancellationToken

CancellationTokenSource

CancellationTokenSource是一个用于创建和控制CancellationToken的类。它是触发取消操作的关键点,并且提供了触发取消操作的方法(如Cancel)和检查是否已请求取消的属性(如IsCancellationRequested)。

主要方法

  • Cancel(): 触发取消操作。
  • Dispose(): 释放CancellationTokenSource占用的资源。

主要属性

  • IsCancellationRequested: 指示是否已请求取消。
  • Token: 获取与此CancellationTokenSource关联的CancellationToken
CancellationToken

CancellationToken是一个轻量级的对象,用于在线程之间传递取消信号。它本身不执行任何取消操作,而是作为取消请求的标记。当某个操作应该被取消时,与该CancellationToken关联的CancellationTokenSource会发出一个取消信号,然后任何监听这个CancellationToken的代码都可以响应这个取消请求。

主要方法

  • Register(Action callback): 注册一个回调,当取消操作被触发时调用该回调。
  • ThrowIfCancellationRequested(): 如果已请求取消,则抛出OperationCanceledException异常。

主要属性

  • IsCancellationRequested: 指示是否已请求取消。

使用示例

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var cts = new CancellationTokenSource();
        var token = cts.Token;

        var task = Task.Run(() => DoWork(token), token);

        // 模拟一段时间后取消操作
        Thread.Sleep(2000);
        cts.Cancel();

        try
        {
            await task; // 如果操作被取消,这里会抛出异常
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation was canceled.");
        }
    }

    static void DoWork(CancellationToken token)
    {
        for (int i = 0; i < 10; i++)
        {
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Cancellation requested.");
                return;
            }
            Thread.Sleep(500); // 模拟工作
            Console.WriteLine($"Working... {i}");
        }
    }
}
当使用 CancellationTokenTask.Run 时,有几个关键点需要注意:
  1. 传递给 Task.RunCancellationToken:这个 CancellationToken 用于控制 Task.Run 创建的任务的生命周期。如果取消这个令牌(即调用 CancellationTokenSource.Cancel()),那么 Task 将接收到一个取消请求,但这并不意味着任务会立即停止执行。任务中的代码需要显式检查这个取消请求(通常通过调用 cancellationToken.ThrowIfCancellationRequested() 或检查 cancellationToken.IsCancellationRequested 属性)并据此决定是否停止执行。

  2. 传递给 DoWork 方法的 CancellationToken:这个 CancellationToken 是作为参数传递给 DoWork 方法的。在 DoWork 方法内部,你可以根据这个令牌的状态来决定是否继续执行。如果检测到取消请求(IsCancellationRequestedtrue),你可以提前退出方法或执行清理操作。但是,仅仅设置这个令牌的取消状态并不会自动停止 DoWork 方法的执行;你需要编写相应的逻辑来处理取消请求。

  3. 任务的取消:即使你取消了 CancellationTokenTask 对象本身也不会立即变为“已取消”状态。相反,它会变为“已取消请求”状态,这意味着已经请求取消该任务,但任务可能仍在执行中。只有当任务中的代码显式检查并响应取消请求时,任务才会停止执行并最终变为“已取消”状态。

  4. 异常处理:如果在任务中检测到取消请求并调用了 cancellationToken.ThrowIfCancellationRequested(),则会抛出一个 OperationCanceledException 异常。这个异常通常被视为正常的取消流程的一部分,而不是一个需要捕获和处理的错误。

因此,总结来说,当 CancellationToken 被取消时,Task.Run 创建的任务会接收到一个取消请求,但 DoWork 方法本身并不会自动停止执行。你需要在 DoWork 方法内部编写逻辑来响应这个取消请求并据此决定是否停止执行。同样,任务本身也不会立即停止执行;它将继续执行直到遇到检查取消请求的代码,并根据该代码的逻辑来决定是否停止。

CancellationToken取消了啥

在C#中,当DoWork方法通过检查CancellationToken.IsCancellationRequested属性并提前返回时,它只是从该方法中退出了。这并不直接结束或取消底层的Task对象。但是,由于Task.Run创建的Task对象是与DoWork方法的执行相关联的,因此当DoWork方法返回时,该Task对象会将其状态设置为“已完成”(RanToCompletion),而不是“已取消”(Canceled)。

然而,如果你想要在检测到取消请求时使Task对象的状态变为“已取消”,你需要抛出一个OperationCanceledException。这通常通过调用CancellationToken.ThrowIfCancellationRequested()方法来实现,该方法在取消请求已发送时会抛出异常。

以下是修改后的DoWork方法示例,它在检测到取消请求时抛出OperationCanceledException

static void DoWork(CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        token.ThrowIfCancellationRequested(); // 如果取消请求已发送,则抛出异常
        
        Console.WriteLine($"DoWork: Working {i + 1}...");
        Thread.Sleep(500); // 模拟耗时操作
    }

    Console.WriteLine("DoWork: Work completed."); // 这行代码实际上不会被执行,因为循环会在某个点抛出异常
}

在这个例子中,如果CancellationToken的取消请求被发送(即调用了CancellationTokenSource.Cancel()方法),ThrowIfCancellationRequested()方法将抛出一个OperationCanceledException异常。这个异常会冒泡到调用Task.Run的代码,并最终导致Task对象的状态变为“已取消”。

但是,请注意,即使你抛出了OperationCanceledExceptionTask对象中的任何本地资源(如文件句柄、数据库连接等)仍然需要由你的代码显式释放。异常只是改变了Task的状态并通知调用者任务已取消,但它不会自动执行任何清理工作。

ManualResetEvent

ManualResetEvent是一个同步基元,它允许线程通过信号进行通信。当事件处于未发出状态时,线程调用WaitOneWaitAnyWaitAll等方法时会阻塞,直到其他线程通过调用Set方法将事件标记为已发出状态。事件保持已发出状态,直到调用Reset方法将其重置为未发出状态。

主要方法

  • Set(): 将ManualResetEvent设置为已发出状态,允许一个或多个等待的线程继续执行。
  • Reset(): 将ManualResetEvent重置为未发出状态,阻塞等待的线程。
  • WaitOne(): 阻塞当前线程,直到ManualResetEvent被设置为已发出状态。

使用示例

using System;
using System.Threading;

class Program
{
    static ManualResetEvent resetEvent = new ManualResetEvent(false);

    static void Main()
    {
        Thread t = new Thread(Worker);
        t.Start();

        // 模拟一些工作...
        Thread.Sleep(2000);

        // 设置事件,允许等待的线程继续执行
        resetEvent.Set();

        Console.WriteLine("Main thread continues...");
    }

    static void Worker()
    {
        Console.WriteLine("Worker thread waiting...");
        resetEvent.WaitOne(); // 等待事件被设置
        Console.WriteLine("Worker thread continues...");
    }
}

在这个示例中,Worker线程在调用resetEvent.WaitOne()时阻塞,直到主线程调用resetEvent.Set()将其设置为已发出状态。

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

語衣

感谢大哥

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值