欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.Net 并行编程系列之 Task取消

程序员文章站 2022-03-16 22:43:42
在Task运行过程中,我们可以通过.Net 4中的内置方法来取消Task的运行。 创建一个可取消的Task需要用到下面的一些对象: 1.System.Threading.CancellationTokenSource实例 2.通过CancellationTokenSource.Token属性获得一个 ......

在Task运行过程中,我们可以通过.Net 4中的内置方法来取消Task的运行。

创建一个可取消的Task需要用到下面的一些对象:

1.System.Threading.CancellationTokenSource实例

CancellationTokenSource tokenSource = new CancellationTokenSource();

2.通过CancellationTokenSource.Token属性获得一个取消令牌

CancellationToken token = tokenSource.Token;

3.创建Task对象,并且在构造函数传入Action(或者Action<T>)委托作为第一个参数,CancellationToken作为第二个参数(重要)

Task task = new Task(() =>
{
// do something
}, token);
task.Start();

 4.创建Task对象也可以通过调用System.Threading.Tasks.TaskFactory 类提供的静态方法

Task task = Task.Factory.StartNew(() =>
{
    // do something ......
}, token);

 

如果想要取消Task的运行,除了要调用CancellationTokenSource实例的Cancel()方法之外,我们的Action委托中还需要检测CancellationToken的取消状态并编写相应代码(抛出异常)来阻止Task的运行。

可以通过以下方式来检测Task取消状态:

1.通过轮询的方式检测CancellationToken取消标记,该操作类似于轮询异步操作的IAsyncResult.IsCompleted状态,也是通过在循环中判断CancellationToken.IsCancellationRequested属性来检测Task是否被取消,如果为True则在Action委托中抛出异常来取消继续运行Task。 

static void Main(string[] args)
{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;
    Task task = new Task(() =>
    {
        while (true)
        {
            if(token.IsCancellationRequested)
            {
                // 释放资源操作等等...
                throw new OperationCanceledException(token);
            }
            Console.Write(".");
            Thread.Sleep(100);
        }
    }, token);

    Console.WriteLine("Task is Running.");
    Console.WriteLine("Press anykey to cancel task.");    
task.Start();
Console.ReadKey(true); Console.WriteLine(); Console.WriteLine("Cancelling task."); tokenSource.Cancel();
Console.WriteLine("Main method complete."); Console.WriteLine("Press enter to finish."); Console.ReadLine(); }

 

如果不需要释放系统资源,那么可以直接调用CancellationToken.ThrowIfCancellationRequested()方法,其实现如下:

[__DynamicallyInvokable]
public void ThrowIfCancellationRequested()
{
    if (this.IsCancellationRequested)
    {
        this.ThrowOperationCanceledException();
    }
}

 示例:

while (true)
{
    token.ThrowIfCancellationRequested();
    Console.Write(".");
    Thread.Sleep(100);
}

 

2.通过委托(Delegate)来检测Task是否取消,注册一个在取消CancellationToken时调用的委托,当CancellationTokenSource发送取消请求时,该委托即会运行,我们可以在委托方法中实现通知功能等等。

static void Main(string[] args)
{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;
    Task task = new Task(() =>
    {
        while (true)
        {
            token.ThrowIfCancellationRequested();
            Console.Write(".");
            Thread.Sleep(100);
        }
    }, token);

    token.Register(() => { Console.WriteLine("The delegate is triggered."); });

    Console.WriteLine("Task is Running.");
    Console.WriteLine("Press anykey to cancel task.");

    task.Start();

    Console.ReadKey(true);
    Console.WriteLine();
    Console.WriteLine("Cancelling task.");
    tokenSource.Cancel();

    Console.WriteLine("Main method complete.");
    Console.WriteLine("Press enter to finish.");
    Console.ReadLine();
}

 

3.用WaitHandle来检测Task是否取消,当在CancellationToken.WaitHandle上调用WaitOne()方法时,会阻止当前线程执行,直到该CancellationToken接收到取消请求标记时,被token阻止的Task线程才会释放并继续执行。

多个Task实例使用同一个CancellationToken时,当CancellationToken接收到取消请求标记时,所有在构造函数中使用该token实例化的Task都会被取消。WaitOne(int millisecondsTimeout)可以使用等待毫秒数作为参数,超过等待时间将会释放阻止的线程。

不管WaitOne(int millisecondsTimeout)设置多长的等待时间,只要CancellationToken接收到取消请求标记时Task都会取消,而如果使用Thread.Sleep(100000)进行线程等待时,那么即使CancellationToken接收到取消请求标记,该Task也会等到Thread.Sleep执行完成才会Cancel。

static void Main(string[] args)
{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;

    Task task = new Task(() =>
    {
        while (true)
        {                    
            token.ThrowIfCancellationRequested();
            Console.Write(".");
            Thread.Sleep(100);
        }
    }, token);

    Task task1 = new Task(() =>
    {
        token.WaitHandle.WaitOne();
        Console.WriteLine("WaitHandle released.");
    }, token);

    Console.WriteLine("Task is Running.");
    Console.WriteLine("Press anykey to cancel task.");

    task.Start();
    task1.Start();

    Console.ReadKey(true);
    Console.WriteLine();
    Console.WriteLine("Cancelling task.");
    tokenSource.Cancel();
Console.WriteLine("Main method complete."); Console.WriteLine("Press enter to finish."); Console.ReadLine(); }

 

4.通过Task的IsCancelled属性来判断Task是否被取消,如果Task实例化时构造函数没有传入CancellationToken对象,则取消Task运行之后通过Task.IsCanceled属性获取到的值还是False而不是TrueTask.ContinueWith()方法如果没有传入CancellationToken对象,则Task即使是取消执行也会继续执行Task.ContinueWith()方法的Action委托,如果传入与Task相同的CancellationToken对象,则Task取消执行后Task.ContinueWith()方法中的Action委托也不会继续执行。

{
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;
    Task task = new Task(() =>
    {
        try
        {
            Console.WriteLine("Task: Running");
            Thread.Sleep(5000);
            Console.WriteLine("Task: ThrowIfCancellationRequested");
            token.ThrowIfCancellationRequested();
            Thread.Sleep(2000);
            Console.WriteLine("Task: Completed");
        }
        catch (Exception exception)
        {
            Console.WriteLine("Task: " + exception.GetType().Name);
            throw;
        }
    }, token);

    task.ContinueWith(t => Console.WriteLine("ContinueWith: tokenSource.IsCancellationRequested = {0}, task.IsCanceled = {1}, task.Exception = {2}", tokenSource.IsCancellationRequested, t.IsCanceled, t.Exception == null ? "null" : t.Exception.GetType().Name));
    task.Start();
    
    Thread.Sleep(1000);

    Console.WriteLine("Main: Cancel");
    tokenSource.Cancel();

    try
    {
        Console.WriteLine("Main: Wait");
        task.Wait();
    }
    catch (Exception exception)
    {
        Console.WriteLine("Main: Catch " + exception.GetType().Name);
    }

    Console.WriteLine("Main: task.IsCanceled = {0}", task.IsCanceled);
    Console.WriteLine("Press any key to exit...");

    Console.ReadKey(true);
}