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

2018/03/05 21:10
阅读数 31

在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);
}

 

通过执行Task.WaitAll(task),Task.WaitAny(task),task.Result,task.Wait()出现了异常抛出的是一个System.AggregateException;

通过执行task.Wait(CancellationToken)出现了异常抛出的是一个OperationCanceledException;

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部