C#多线程编程系列(五)- 使用任务并行库 (5)

1533778462479

1.6 将EAP模式转换为任务

在上几章中有提到,通过BackgroundWorker类通过事件的方式实现的异步,我们叫它EAP模式。那么如何将EAP模式转换为任务呢?很简单,我们只需要通过TaskCompletionSource类,即可将EAP模式转换为任务。

演示代码如下所示。

static void Main(string[] args) { var tcs = new TaskCompletionSource<int>(); var worker = new BackgroundWorker(); worker.DoWork += (sender, eventArgs) => { eventArgs.Result = TaskMethod("后台工作", 5); }; // 通过此方法 将EAP模式转换为 任务 worker.RunWorkerCompleted += (sender, eventArgs) => { if (eventArgs.Error != null) { tcs.SetException(eventArgs.Error); } else if (eventArgs.Cancelled) { tcs.SetCanceled(); } else { tcs.SetResult((int)eventArgs.Result); } }; worker.RunWorkerAsync(); // 调用结果 int result = tcs.Task.Result; WriteLine($"结果是:{result}"); ReadLine(); } static int TaskMethod(string name, int seconds) { WriteLine($"任务{name}运行在线程{CurrentThread.ManagedThreadId}上. 是否为线程池线程{CurrentThread.IsThreadPoolThread}"); Sleep(TimeSpan.FromSeconds(seconds)); return 42 * seconds; }

运行结果如下图所示。

1533785637929

1.7 实现取消选项

在TAP模式中,实现取消选项和之前的异步模式一样,都是使用CancellationToken来实现,但是不同的是Task构造函数允许传入一个CancellationToken,从而在任务实际启动之前取消它。

演示代码如下所示。

static void Main(string[] args) { var cts = new CancellationTokenSource(); // new Task时 可以传入一个 CancellationToken对象 可以在线程创建时 变取消任务 var longTask = new Task<int>(() => TaskMethod("Task 1", 10, cts.Token), cts.Token); WriteLine(longTask.Status); cts.Cancel(); WriteLine(longTask.Status); WriteLine("第一个任务在运行前被取消."); // 同样的 可以通过CancellationToken对象 取消正在运行的任务 cts = new CancellationTokenSource(); longTask = new Task<int>(() => TaskMethod("Task 2", 10, cts.Token), cts.Token); longTask.Start(); for (int i = 0; i < 5; i++) { Sleep(TimeSpan.FromSeconds(0.5)); WriteLine(longTask.Status); } cts.Cancel(); for (int i = 0; i < 5; i++) { Sleep(TimeSpan.FromSeconds(0.5)); WriteLine(longTask.Status); } WriteLine($"这个任务已完成,结果为{longTask.Result}"); ReadLine(); } static int TaskMethod(string name, int seconds, CancellationToken token) { WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}"); for (int i = 0; i < seconds; i++) { Sleep(TimeSpan.FromSeconds(1)); if (token.IsCancellationRequested) { return -1; } } return 42 * seconds; }

运行结果如下图所示,这里需要注意的是,如果是在任务执行之前取消了任务,那么它的最终状态是Canceled。如果是在执行过程中取消任务,那么它的状态是RanCompletion。

1533783996906

1.8 处理任务中的异常

在任务中,处理异常和其它异步方式处理异常类似,如果能在所发生异常的线程中处理,那么不要在其它地方处理。但是对于一些不可预料的异常,那么可以通过几种方式来处理。

可以通过访问task.Result属性来处理异常,因为访问这个属性的Get方法会使当前线程等待直到该任务完成,并将异常传播给当前线程,这样就可以通过try catch语句块来捕获异常。另外使用task.GetAwaiter().GetResult()方法和第使用task.Result类似,同样可以捕获异常。如果是要捕获多个任务中的异常错误,那么可以通过ContinueWith()方法来处理。

具体如何实现,演示代码如下所示。

static void Main(string[] args) { Task<int> task; // 在主线程中调用 task.Result task中的异常信息会直接抛出到 主线程中 try { task = Task.Run(() => TaskMethod("Task 1", 2)); int result = task.Result; WriteLine($"结果为: {result}"); } catch (Exception ex) { WriteLine($"异常被捕捉:{ex.Message}"); } WriteLine("------------------------------------------------"); WriteLine(); // 同上 只是访问Result的方式不同 try { task = Task.Run(() => TaskMethod("Task 2", 2)); int result = task.GetAwaiter().GetResult(); WriteLine($"结果为:{result}"); } catch (Exception ex) { WriteLine($"异常被捕捉: {ex.Message}"); } WriteLine("----------------------------------------------"); WriteLine(); var t1 = new Task<int>(() => TaskMethod("Task 3", 3)); var t2 = new Task<int>(() => TaskMethod("Task 4", 4)); var complexTask = Task.WhenAll(t1, t2); // 通过ContinueWith TaskContinuationOptions.OnlyOnFaulted的方式 如果task出现异常 那么才会执行该方法 var exceptionHandler = complexTask.ContinueWith(t => { WriteLine($"异常被捕捉:{t.Exception.Message}"); foreach (var ex in t.Exception.InnerExceptions) { WriteLine($"-------------------------- {ex.Message}"); } },TaskContinuationOptions.OnlyOnFaulted); t1.Start(); t2.Start(); ReadLine(); } static int TaskMethod(string name, int seconds) { WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}"); Sleep(TimeSpan.FromSeconds(seconds)); // 人为抛出一个异常 throw new Exception("Boom!"); return 42 * seconds; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzzfj.html