博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.NET 并行(多核)编程系列之五 Task执行和异常处理
阅读量:5995 次
发布时间:2019-06-20

本文共 7937 字,大约阅读时间需要 26 分钟。

原文:

.NET 并行(多核)编程系列之五 Task执行和异常处理

  前言:本篇主要讲述等待task执行完成。

  本篇的议题如下:
  1. 等待Task执行完成
  2. Task中的异常处理

 

  系列文章链接:

  

 

 

 

 

 

 

  首先注意一点:这里提到的"等待"和之前文章提到的"休眠"意思是不一样的:

  等待:在等待一个task的时候,这个task还是在运行之中的,"等待"相当于在监听运行的task的执行情况。
  休眠:让tasku不运行。

 

  在上篇文章中介绍了如果从Task中获取执行后的结果:在Task执行完成之后调用Task.Result获取。其实也可以用其他的方法等待Task执行完成而不获取结果,这是很有用的:如果你想等待一个task完成之后再去做其他的事情。而且我们还可以等待一个task执行完成,或者等待所有的task执行完成,或者等待很多task中的一个执行完成。因为Task是由内部的Scheduler管理的,调用wait方法,其实就是我们在监控task的执行,看看这个task是否执行完了,如果完成,那么wanit方法就返回true,反之。

 

  1. 等待Task执行完成

  1.1等待单独的一个Task执行完成

  我们可以用Wait()方法来一直等待一个Task执行完成。当task执行完成,或者被cancel,或者抛出异常,这个方法才会返回。可以使用Wait()方法的不同重载。举个例子:
 

 

ExpandedBlockStart.gif
代码
 
static
 
void
 Main(
string
[] args)
    {
        
//
 create the cancellation token source
        CancellationTokenSource tokenSource 
=
 
new
 CancellationTokenSource();
        
//
 create the cancellation token
        CancellationToken token 
=
 tokenSource.Token;
        
//
 create and start the first task, which we will let run fully
        Task task 
=
 createTask(token);
        task.Start();
        
//
 wait for the task
        Console.WriteLine(
"
Waiting for task to complete.
"
);
        task.Wait();
        Console.WriteLine(
"
Task Completed.
"
);
        
//
 create and start another task
        task 
=
 createTask(token);
        task.Start();
        Console.WriteLine(
"
Waiting 2 secs for task to complete.
"
);
        
bool
 completed 
=
 task.Wait(
2000
);
        Console.WriteLine(
"
Wait ended - task completed: {0}
"
, completed);
        
//
 create and start another task
        task 
=
 createTask(token);
        task.Start();
        Console.WriteLine(
"
Waiting 2 secs for task to complete.
"
);
        completed 
=
 task.Wait(
2000
, token);
        Console.WriteLine(
"
Wait ended - task completed: {0} task cancelled {1}
"
,
        completed, task.IsCanceled);
        
//
 wait for input before exiting
        Console.WriteLine(
"
Main method complete. Press enter to finish.
"
);
        Console.ReadLine();
    }
    
static
 Task createTask(CancellationToken token)
    {
        
return
 
new
 Task(() 
=>
        {
            
for
 (
int
 i 
=
 
0
; i 
<
 
5
; i
++
)
            {
                
//
 check for task cancellation
                token.ThrowIfCancellationRequested();
                
//
 print out a message
                Console.WriteLine(
"
Task - Int value {0}
"
, i);
                
//
 put the task to sleep for 1 second
                token.WaitHandle.WaitOne(
1000
);
            }
        }, token);
    }

 

  

  从上面的例子可以看出,wait方法子task执行完成之后会返回true。

  注意:当在执行的task内部抛出了异常之后,这个异常在调用wait方法时会被再次抛出。后面再"异常处理篇"会讲述。

 

  1.2.等待多个task

  我们也可以用WaitAll()方法来一直到等待多个task执行完成。只有当所有的task执行完成,或者被cancel,或者抛出异常,这个方法才会返回。WiatAll()方法和Wait()方法一样有一些重载。
  注意:如果在等在的多个task之中,有一个task抛出了异常,那么调用WaitAll()方法时就会抛出异常。

 

ExpandedBlockStart.gif
代码
static
 
void
 Main(
string
[] args)
    {
        
//
 create the cancellation token source
        CancellationTokenSource tokenSource 
=
 
new
 CancellationTokenSource();
        
//
 create the cancellation token
        CancellationToken token 
=
 tokenSource.Token;
        
//
 create the tasks
        Task task1 
=
 
new
 Task(() 
=>
        {
            
for
 (
int
 i 
=
 
0
; i 
<
 
5
; i
++
)
            {
                
//
 check for task cancellation
                token.ThrowIfCancellationRequested();
                
//
 print out a message
                Console.WriteLine(
"
Task 1 - Int value {0}
"
, i);
                
//
 put the task to sleep for 1 second
                token.WaitHandle.WaitOne(
1000
);
            }
            Console.WriteLine(
"
Task 1 complete
"
);
        }, token);
        Task task2 
=
 
new
 Task(() 
=>
        {
            Console.WriteLine(
"
Task 2 complete
"
);
        }, token);
        
//
 start the tasks
        task1.Start();
        task2.Start();
        
//
 wait for the tasks
        Console.WriteLine(
"
Waiting for tasks to complete.
"
);
        Task.WaitAll(task1, task2);
        Console.WriteLine(
"
Tasks Completed.
"
);
        
//
 wait for input before exiting
        Console.WriteLine(
"
Main method complete. Press enter to finish.
"
);
        Console.ReadLine();
    }

 

   在上面的例子中,首先创建了两个task,注意我们创建的是可以被cancel的task,因为使用CancellationToken。而且在第一个task中还是用waitOne()休眠方法,其实目的很简单:使得这个task的运行时间长一点而已。之后我们就调用了WaitAll()方法,这个方法一直到两个task执行完成之后才会返回的。

 

  1.3.等待多个task中的一个task执行完成

  可以用WaitAny()方法来等待多个task中的一个task执行完成。通俗的讲就是:有很多的task在运行,调用了WaitAny()方法之后,只要那些运行的task其中有一个运行完成了,那么WaitAny()就返回了。
 

ExpandedBlockStart.gif
代码
static
 
void
 Main(
string
[] args)
    {
        
//
 create the cancellation token source
        CancellationTokenSource tokenSource 
=
 
new
 CancellationTokenSource();
        
//
 create the cancellation token
        CancellationToken token 
=
 tokenSource.Token;
        
//
 create the tasks
        Task task1 
=
 
new
 Task(() 
=>
        {
            
for
 (
int
 i 
=
 
0
; i 
<
 
5
; i
++
)
            {
                
//
 check for task cancellation
                token.ThrowIfCancellationRequested();
                
//
 print out a message
                Console.WriteLine(
"
Task 1 - Int value {0}
"
, i);
                
//
 put the task to sleep for 1 second
                token.WaitHandle.WaitOne(
1000
);
            }
            Console.WriteLine(
"
Task 1 complete
"
);
        }, token);
        Task task2 
=
 
new
 Task(() 
=>
        {
            Console.WriteLine(
"
Task 2 complete
"
);
        }, token);
        
//
 start the tasks
        task1.Start();
        task2.Start();
        
//
 wait for the tasks
        Console.WriteLine(
"
Waiting for tasks to complete.
"
);
        
int
 taskIndex 
=
 Task.WaitAny(task1, task2);
        Console.WriteLine(
"
Task Completed. Index: {0}
"
, taskIndex);
        
//
 wait for input before exiting
        Console.WriteLine(
"
Main method complete. Press enter to finish.
"
);
        Console.ReadLine();
    }

 

  

  2. Task中的异常处理

  在并行编程(TPL)中另外一个已经标准化了的操作就是"异常处理"。而且在并行编程中异常处理显得尤为重要,因为并行编程时与系统中的线程相关的,出了异常,你开发的程序就很有可能崩溃。

 

  下面就详细介绍TPL中异常处理操作。

   a.处理基本的异常。
   在操作task的时候,只要出现了异常,.NET Framework就会把这些异常记录下来。例如在执行Task.Wait(),Task.WaitAll(),Task.WaitAny(),Task.Result.不管那里出现了异常,最后抛出的就是一个System.AggregateException.

   System.AggregateException时用来包装一个或者多个异常的,这个类时很有用的,特别是在调用Task.WaitAll()方法时。因为在Task.WaitAll()是等待多个task执行完成,如果有任意task执行出了异常,那么这个异常就会被记录在System.AggregateException中,不同的task可能抛出的异常不同,但是这些异常都会被记录下来。

  下面就是给出一个例子:在例子中,创建了两个task,它们都抛出异常。然后主线程开始运行task,并且调用WaitAll()方法,然后就捕获抛出的System.AggregateException,显示详细信息。
 

 

ExpandedBlockStart.gif
代码
static
 
void
 Main(
string
[] args)
    {
        
//
 create the tasks
        Task task1 
=
 
new
 Task(() 
=>
        {
            ArgumentOutOfRangeException exception 
=
 
new
 ArgumentOutOfRangeException();
            exception.Source 
=
 
"
task1
"
;
            
throw
 exception;
        });
        Task task2 
=
 
new
 Task(() 
=>
        {
            
throw
 
new
 NullReferenceException();
        });
        Task task3 
=
 
new
 Task(() 
=>
        {
            Console.WriteLine(
"
Hello from Task 3
"
);
        });
        
//
 start the tasks
        task1.Start(); task2.Start(); task3.Start();
        
//
 wait for all of the tasks to complete
        
//
 and wrap the method in a try...catch block
        
try
        {
            Task.WaitAll(task1, task2, task3);
        }
        
catch
 (AggregateException ex)
        {
            
//
 enumerate the exceptions that have been aggregated
            
foreach
 (Exception inner 
in
 ex.InnerExceptions)
            {
                Console.WriteLine(
"
Exception type {0} from {1}
"
,
                inner.GetType(), inner.Source);
            }
        }
        
//
 wait for input before exiting
        Console.WriteLine(
"
Main method complete. Press enter to finish.
"
);
        Console.ReadLine();
    }

 

 

 从上面的例子可以看出,为了获得被包装起来的异常,需要调用System.AggregateException的InnerExceptions属性,这个属性返回一个异常的集合,然后就可以遍历这个集合。

  而且从上面的例子可以看到:Exeception.Source属性被用来指明task1的异常时ArgumentOutRangeException.

   b.使用迭代的异常处理Handler
   一般情况下,我们需要区分哪些异常需要处理,而哪些异常需要继续往上传递。AggregateException类提供了一个Handle()方法,我们可以用这个方法来处理

AggregateException中的每一个异常。在这个Handle()方法中,返回true就表明,这个异常我们已经处理了,不用抛出,反之。

   在下面的例子中,抛出了一个OperationCancelException,在之前的task的取消一文中,已经提到过:当在task中抛出这个异常的时候,实际上就是这个task发送了取消的请求。下面的代码中,描述了如果在AggregateException.Handle()中处理不同的异常。

 

 

ExpandedBlockStart.gif
代码
static
 
void
 Main(
string
[] args)
    {
        
//
 create the cancellation token source and the token
        CancellationTokenSource tokenSource 
=
 
new
 CancellationTokenSource();
        CancellationToken token 
=
 tokenSource.Token;
        
//
 create a task that waits on the cancellation token
        Task task1 
=
 
new
 Task(() 
=>
        {
            
//
 wait forever or until the token is cancelled
            token.WaitHandle.WaitOne(
-
1
);
            
//
 throw an exception to acknowledge the cancellation
            
throw
 
new
 OperationCanceledException(token);
        }, token);
        
//
 create a task that throws an exception
        Task task2 
=
 
new
 Task(() 
=>
        {
            
throw
 
new
 NullReferenceException();
        });
        
//
 start the tasks
        task1.Start(); task2.Start();
        
//
 cancel the token
        tokenSource.Cancel();
        
//
 wait on the tasks and catch any exceptions
        
try
        {
            Task.WaitAll(task1, task2);
        }
        
catch
 (AggregateException ex)
        {
            
//
 iterate through the inner exceptions using
            
//
 the handle method
            ex.Handle((inner) 
=>
            {
                
if
 (inner 
is
 OperationCanceledException)
                {
                    
//
 ...handle task cancellation...
                    
return
 
true
;
                }
                
else
                {
                    
//
 this is an exception we don't know how
                    
//
 to handle, so return false
                    
return
 
false
;
                }
            });
        }
        
//
 wait for input before exiting
        Console.WriteLine(
"
Main method complete. Press enter to finish.
"
);
        Console.ReadLine();
    }

 

 

   今天就写到这里。谢谢!

   版权为小洋和博客园所有,转载请标明出处给作者。

   

你可能感兴趣的文章
lisp中的字符串和字符串操作
查看>>
如何优雅的在 koa 中处理错误
查看>>
JProfiler 监控Tomcat
查看>>
③云上场景:天弘基金,支撑余额宝的弹性扩展架构
查看>>
InnoDB中dict_update_statistics调用策略及问题
查看>>
Docker安装前升级内核3.10
查看>>
【SICP练习】30 练习1.36
查看>>
Windows下SonarQube与Jenkins环境的配置使用
查看>>
关于散列表的一些思考
查看>>
真香!Kotlin+MVVM+LiveData+协程 打造 Wanandroid!
查看>>
iOS学习资源(三)
查看>>
AndroidStudio 性能优化指南(Windows 篇)
查看>>
在K8S上搭建Redis集群
查看>>
Vue-router create-route-map 源码分享
查看>>
Spring Cloud Netflix—注册安全应用程序
查看>>
rpop 和 brpop的区别
查看>>
iOS内购开发以及遇到的问题
查看>>
cookie 简介
查看>>
ios和android内嵌h5页面联调小结
查看>>
PHP SG扩展管理Superglobals
查看>>