上海工程建设造价信息网站知识营销
news/
2025/9/30 14:37:54/
文章来源:
上海工程建设造价信息网站,知识营销,微信群 网站建设,用什么网站做海报目录
1.1 简介1.2 创建任务1.3 使用任务执行基本的操作1.4 组合任务1.5 将APM模式转换为任务1.6 将EAP模式转换为任务1.7 实现取消选项1.8 处理任务中的异常1.9 并行运行任务1.10 使用TaskScheduler配置任务执行参考书籍笔者水平有限#xff0c;如果错误欢迎各位批评指正如果错误欢迎各位批评指正本系列首页链接[C#多线程编程系列一- 简介 ] 1.1 简介#
在之前的几个章节中就线程的使用和多线程相关的内容进行了介绍。因为线程涉及到异步、同步、异常传递等问题所以在项目中使用多线程的代价是比较高昂的需要编写大量的代码来达到正确性和健壮性。
为了解决这样一些的问题在.Net Framework 4.0中引入了一个关于一步操作的API。它叫做任务并行库(Task Parallel Library)。然后在.Net Framwork 4.5中对它进行了轻微的改进本文的案例都是用最新版本的TPL库而且我们还可以使用C# 5.0的新特性await/async来简化TAP编程当然这是之后才介绍的。
TPL内部使用了线程池但是效率更高。在把线程归还回线程池之前它会在同一线程中顺序执行多少Task这样避免了一些小任务上下文切换浪费时间片的问题。
任务是对象其中封装了以异步方式执行的工作但是委托也是封装了代码的对象。任务和委托的区别在于委托是同步的而任务是异步的。
在本章中我们将会讨论如何使用TPL库来进行任务之间的组合同步如何将遗留的APM和EAP模式转换为TPL模式等等。
1.2 创建任务#
在本节中主要是演示了如何创建一个任务。其主要用到了System.Threading.Tasks命名空间下的Task类。该类可以被实例化并且提供了一组静态方法可以方便快捷的创建任务。
在下面实例代码中分别延时了三种常见的任务创建方式并且创建任务是可以指定任务创建的选项从而达到最优的创建方式。
在TaskCreationOptions中一共有7个枚举枚举是可以使用|运算符组合定义的。其枚举如下表所示。
成员名称说明AttachedToParent指定将任务附加到任务层次结构中的某个父级。 默认情况下子任务即由外部任务创建的内部任务将独立于其父任务执行。 可以使用 TaskContinuationOptions.AttachedToParent 选项以便将父任务和子任务同步。请注意如果使用 DenyChildAttach 选项配置父任务则子任务中的 AttachedToParent 选项不起作用并且子任务将作为分离的子任务执行。有关详细信息请参阅附加和分离的子任务。DenyChildAttach指定任何尝试作为附加的子任务执行即使用 AttachedToParent 选项创建的子任务都无法附加到父任务会改成作为分离的子任务执行。 有关详细信息请参阅附加和分离的子任务。HideScheduler防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 Default 当前计划程序。LongRunning指定任务将是长时间运行的、粗粒度的操作涉及比细化的系统更少、更大的组件。 它会向 TaskScheduler 提示过度订阅可能是合理的。 可以通过过度订阅创建比可用硬件线程数更多的线程。 它还将提示任务计划程序该任务需要附加线程以使任务不阻塞本地线程池队列中其他线程或工作项的向前推动。None指定应使用默认行为。PreferFairness提示 TaskScheduler 以一种尽可能公平的方式安排任务这意味着较早安排的任务将更可能较早运行而较晚安排运行的任务将更可能较晚运行。RunContinuationsAsynchronously强制异步执行添加到当前任务的延续任务。请注意RunContinuationsAsynchronously 成员在以 .NET Framework 4.6 开头的 TaskCreationOptions 枚举中可用。Copy
static void Main(string[] args) { // 使用构造方法创建任务 var t1 new Task(() TaskMethod(Task 1)); var t2 new Task(() TaskMethod(Task 2)); // 需要手动启动 t2.Start(); t1.Start(); // 使用Task.Run 方法启动任务 不需要手动启动 Task.Run(() TaskMethod(Task 3)); // 使用 Task.Factory.StartNew方法 启动任务 实际上就是Task.Run Task.Factory.StartNew(() TaskMethod(Task 4)); // 在StartNew的基础上 添加 TaskCreationOptions.LongRunning 告诉 Factory该任务需要长时间运行 // 那么它就会可能会创建一个 非线程池线程来执行任务 Task.Factory.StartNew(() TaskMethod(Task 5), TaskCreationOptions.LongRunning); ReadLine(); } static void TaskMethod(string name) { WriteLine($任务 {name} 运行线程 id {CurrentThread.ManagedThreadId}. 是否为线程池线程: {CurrentThread.IsThreadPoolThread}.); }
运行结果如下图所示。 1.3 使用任务执行基本的操作#
在本节中使用任务执行基本的操作并且获取任务执行完成后的结果值。本节内容比较简单在此不做过多介绍。
演示代码如下在主线程中要获取结果值常用的方式就是访问task.Result属性如果任务线程还没执行完毕那么会阻塞主线程直到线程执行完。如果任务线程执行完毕那么将直接拿到运算的结果值。
在Task 3中使用了task.Status来打印线程的状态线程每个状态的具体含义将在下一节中介绍。 Copy
static void Main(string[] args) { // 直接执行方法 作为参照 TaskMethod(主线程任务); // 访问 Result属性 达到运行结果 Taskint task CreateTask(Task 1); task.Start(); int result task.Result; WriteLine($运算结果: {result}); // 使用当前线程同步执行任务 task CreateTask(Task 2); task.RunSynchronously(); result task.Result; WriteLine($运算结果{result}); // 通过循环等待 获取运行结果 task CreateTask(Task 3); WriteLine(task.Status); task.Start(); while (!task.IsCompleted) { WriteLine(task.Status); Sleep(TimeSpan.FromSeconds(0.5)); } WriteLine(task.Status); result task.Result; WriteLine($运算结果{result}); Console.ReadLine(); } static Taskint CreateTask(string name) { return new Taskint(() TaskMethod(name)); } static int TaskMethod(string name) { WriteLine(${name} 运行在线程 {CurrentThread.ManagedThreadId}上. 是否为线程池线程{CurrentThread.IsThreadPoolThread}); Sleep(TimeSpan.FromSeconds(2)); return 42; }
运行结果如下可见Task 1 和Task 2均是运行在主线程上并非线程池线程。 1.4 组合任务#
在本节中体现了任务其中一个强大的功能那就是组合任务。通过组合任务可很好的描述任务与任务之间的异步、同步关系大大降低了编程的难度。
组合任务主要是通过task.ContinueWith()、task.WhenAny()、task.WhenAll()等和task.GetAwaiter().OnCompleted()方法来实现。
在使用task.ContinueWith()方法时需要注意它也可传递一系列的枚举选项TaskContinuationOptions该枚举选项和TaskCreationOptions类似其具体定义如下表所示。
成员名称说明AttachedToParent如果延续为子任务则指定将延续附加到任务层次结构中的父级。 只有当延续前面的任务也是子任务时延续才可以是子任务。 默认情况下子任务即由外部任务创建的内部任务将独立于其父任务执行。 可以使用 TaskContinuationOptions.AttachedToParent 选项以便将父任务和子任务同步。请注意如果使用 DenyChildAttach 选项配置父任务则子任务中的 AttachedToParent 选项不起作用并且子任务将作为分离的子任务执行。有关更多信息请参见Attached and Detached Child Tasks。DenyChildAttach指定任何使用 TaskCreationOptions.AttachedToParent 选项创建并尝试作为附加的子任务执行的子任务即由此延续创建的任何嵌套内部任务都无法附加到父任务会改成作为分离的子任务执行。 有关详细信息请参阅附加和分离的子任务。ExecuteSynchronously指定应同步执行延续任务。 指定此选项后延续任务在导致前面的任务转换为其最终状态的相同线程上运行。如果在创建延续任务时已经完成前面的任务则延续任务将在创建此延续任务的线程上运行。 如果前面任务的 CancellationTokenSource 已在一个 finally在 Visual Basic 中为 Finally块中释放则使用此选项的延续任务将在该 finally 块中运行。 只应同步执行运行时间非常短的延续任务。由于任务以同步方式执行因此无需调用诸如 Task.Wait 的方法来确保调用线程等待任务完成。HideScheduler指定由延续通过调用方法如 Task.Run 或 Task.ContinueWith创建的任务将默认计划程序 (TaskScheduler.Default) 视为当前的计划程序而不是正在运行该延续的计划程序。LazyCancellation在延续取消的情况下防止延续的完成直到完成先前的任务。LongRunning指定延续将是长期运行的、粗粒度的操作。 它会向 TaskScheduler 提示过度订阅可能是合理的。None如果未指定延续选项应在执行延续任务时使用指定的默认行为。 延续任务在前面的任务完成后以异步方式运行与前面任务最终的 Task.Status 属性值无关。 如果延续为子任务则会将其创建为分离的嵌套任务。NotOnCanceled指定不应在延续任务前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled则前面的任务会取消。 此选项对多任务延续无效。NotOnFaulted指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted则前面的任务会引发未处理的异常。 此选项对多任务延续无效。NotOnRanToCompletion指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion则前面的任务会运行直至完成。 此选项对多任务延续无效。OnlyOnCanceled指定只应在延续前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled则前面的任务会取消。 此选项对多任务延续无效。OnlyOnFaulted指定只有在延续任务前面的任务引发了未处理异常的情况下才应安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted则前面的任务会引发未处理的异常。OnlyOnFaulted 选项可保证前面任务中的 Task.Exception 属性不是 null。 你可以使用该属性来捕获异常并确定导致任务出错的异常。 如果你不访问 Exception 属性则不会处理异常。 此外如果尝试访问已取消或出错的任务的 Result 属性则会引发一个新异常。此选项对多任务延续无效。OnlyOnRanToCompletion指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion则前面的任务会运行直至完成。 此选项对多任务延续无效。PreferFairness提示 TaskScheduler 按任务计划的顺序安排任务因此较早安排的任务将更可能较早运行而较晚安排运行的任务将更可能较晚运行。RunContinuationsAsynchronously指定应异步运行延续任务。 此选项优先于 TaskContinuationOptions.ExecuteSynchronously。
演示代码如下所示使用ContinueWith()和OnCompleted()方法组合了任务来运行搭配不同的TaskCreationOptions和TaskContinuationOptions来实现不同的效果。 Copy
static void Main(string[] args) { WriteLine($主线程 线程 Id {CurrentThread.ManagedThreadId}); // 创建两个任务 var firstTask new Taskint(() TaskMethod(Frist Task,3)); var secondTask new Taskint(() TaskMethod(Second Task,2)); // 在默认的情况下 ContiueWith会在前面任务运行后再运行 firstTask.ContinueWith(t WriteLine($第一次运行答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程: {CurrentThread.IsThreadPoolThread})); // 启动任务 firstTask.Start(); secondTask.Start(); Sleep(TimeSpan.FromSeconds(4)); // 这里会紧接着 Second Task运行后运行 但是由于添加了 OnlyOnRanToCompletion 和 ExecuteSynchronously 所以会由运行SecondTask的线程来 运行这个任务 Task continuation secondTask.ContinueWith(t WriteLine($第二次运行的答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程{CurrentThread.IsThreadPoolThread}),TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); // OnCompleted 是一个事件 当contiuation运行完成后 执行OnCompleted Action事件 continuation.GetAwaiter().OnCompleted(() WriteLine($后继任务完成. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程 {CurrentThread.IsThreadPoolThread})); Sleep(TimeSpan.FromSeconds(2)); WriteLine(); firstTask new Taskint(() { // 使用了TaskCreationOptions.AttachedToParent 将这个Task和父Task关联 当这个Task没有结束时 父Task 状态为 WaitingForChildrenToComplete var innerTask Task.Factory.StartNew(() TaskMethod(Second Task,5), TaskCreationOptions.AttachedToParent); innerTask.ContinueWith(t TaskMethod(Thrid Task, 2), TaskContinuationOptions.AttachedToParent); return TaskMethod(First Task,2); }); firstTask.Start(); // 检查firstTask线程状态 根据上面的分析 首先是 Running - WatingForChildrenToComplete - RanToCompletion while (! firstTask.IsCompleted) { WriteLine(firstTask.Status); Sleep(TimeSpan.FromSeconds(0.5)); } WriteLine(firstTask.Status); Console.ReadLine(); } static int TaskMethod(string name, int seconds) { WriteLine($任务 {name} 正在运行,线程池线程 Id {CurrentThread.ManagedThreadId},是否为线程池线程: {CurrentThread.IsThreadPoolThread}); Sleep(TimeSpan.FromSeconds(seconds)); return 42 * seconds; }
运行结果如下图所示与预期结果一致。其中使用了task.Status来打印任务运行的状态对于task.Status的状态具体含义如下表所示。
成员名称说明Canceled该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认此时该标记处于已发送信号状态或者在该任务开始执行之前已向该任务的 CancellationToken 发出了信号。 有关详细信息请参阅任务取消。Created该任务已初始化但尚未被计划。Faulted由于未处理异常的原因而完成的任务。RanToCompletion已成功完成执行的任务。Running该任务正在运行但尚未完成。WaitingForActivation该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。WaitingForChildrenToComplete该任务已完成执行正在隐式等待附加的子任务完成。WaitingToRun该任务已被计划执行但尚未开始执行。1.5 将APM模式转换为任务#
在前面的章节中介绍了基于IAsyncResult接口实现了BeginXXXX/EndXXXX方法的就叫APM模式。APM模式非常古老那么如何将它转换为TAP模式呢对于常见的几种APM模式异步任务我们一般选择使用Task.Factory.FromAsync()方法来实现将APM模式转换为TAP模式。
演示代码如下所示比较简单不作过多介绍。 Copy
static void Main(string[] args) { int threadId; AsynchronousTask d Test; IncompatibleAsychronousTask e Test; // 使用 Task.Factory.FromAsync方法 转换为Task WriteLine(Option 1); Taskstring task Taskstring.Factory.FromAsync(d.BeginInvoke(异步任务线程, CallBack, 委托异步调用), d.EndInvoke); task.ContinueWith(t WriteLine($回调函数执行完毕现在运行续接函数结果{t.Result})); while (!task.IsCompleted) { WriteLine(task.Status); Sleep(TimeSpan.FromSeconds(0.5)); } WriteLine(task.Status); Sleep(TimeSpan.FromSeconds(1)); WriteLine(----------------------------------------------); WriteLine(); // 使用 Task.Factory.FromAsync重载方法 转换为Task WriteLine(Option 2); task Taskstring.Factory.FromAsync(d.BeginInvoke,d.EndInvoke,异步任务线程,委托异步调用); task.ContinueWith(t WriteLine($任务完成现在运行续接函数结果{t.Result})); while (!task.IsCompleted) { WriteLine(task.Status); Sleep(TimeSpan.FromSeconds(0.5)); } WriteLine(task.Status); Sleep(TimeSpan.FromSeconds(1)); WriteLine(----------------------------------------------); WriteLine(); // 同样可以使用 FromAsync方法 将 BeginInvoke 转换为 IAsyncResult 最后转换为 Task WriteLine(Option 3); IAsyncResult ar e.BeginInvoke(out threadId, CallBack, 委托异步调用); task Taskstring.Factory.FromAsync(ar, _ e.EndInvoke(out threadId, ar)); task.ContinueWith(t WriteLine($任务完成现在运行续接函数结果{t.Result}线程Id {threadId})); while (!task.IsCompleted) { WriteLine(task.Status); Sleep(TimeSpan.FromSeconds(0.5)); } WriteLine(task.Status); ReadLine(); } delegate string AsynchronousTask(string threadName); delegate string IncompatibleAsychronousTask(out int threadId); static void CallBack(IAsyncResult ar) { WriteLine(开始运行回调函数...); WriteLine($传递给回调函数的状态{ar.AsyncState}); WriteLine($是否为线程池线程{CurrentThread.IsThreadPoolThread}); WriteLine($线程池工作线程Id{CurrentThread.ManagedThreadId}); } static string Test(string threadName) { WriteLine(开始运行...); WriteLine($是否为线程池线程{CurrentThread.IsThreadPoolThread}); Sleep(TimeSpan.FromSeconds(2)); CurrentThread.Name threadName; return $线程名{CurrentThread.Name}; } static string Test(out int threadId) { WriteLine(开始运行...); WriteLine($是否为线程池线程{CurrentThread.IsThreadPoolThread}); Sleep(TimeSpan.FromSeconds(2)); threadId CurrentThread.ManagedThreadId; return $线程池线程工作Id是{threadId}; }
运行结果如下图所示。 1.6 将EAP模式转换为任务#
在上几章中有提到通过BackgroundWorker类通过事件的方式实现的异步我们叫它EAP模式。那么如何将EAP模式转换为任务呢很简单我们只需要通过TaskCompletionSource类即可将EAP模式转换为任务。
演示代码如下所示。 Copy
static void Main(string[] args) { var tcs new TaskCompletionSourceint(); 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; }
运行结果如下图所示。 1.7 实现取消选项#
在TAP模式中实现取消选项和之前的异步模式一样都是使用CancellationToken来实现但是不同的是Task构造函数允许传入一个CancellationToken从而在任务实际启动之前取消它。
演示代码如下所示。 Copy
static void Main(string[] args) { var cts new CancellationTokenSource(); // new Task时 可以传入一个 CancellationToken对象 可以在线程创建时 变取消任务 var longTask new Taskint(() TaskMethod(Task 1, 10, cts.Token), cts.Token); WriteLine(longTask.Status); cts.Cancel(); WriteLine(longTask.Status); WriteLine(第一个任务在运行前被取消.); // 同样的 可以通过CancellationToken对象 取消正在运行的任务 cts new CancellationTokenSource(); longTask new Taskint(() 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。 1.8 处理任务中的异常#
在任务中处理异常和其它异步方式处理异常类似如果能在所发生异常的线程中处理那么不要在其它地方处理。但是对于一些不可预料的异常那么可以通过几种方式来处理。
可以通过访问task.Result属性来处理异常因为访问这个属性的Get方法会使当前线程等待直到该任务完成并将异常传播给当前线程这样就可以通过try catch语句块来捕获异常。另外使用task.GetAwaiter().GetResult()方法和第使用task.Result类似同样可以捕获异常。如果是要捕获多个任务中的异常错误那么可以通过ContinueWith()方法来处理。
具体如何实现演示代码如下所示。 Copy
static void Main(string[] args) { Taskint 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 Taskint(() TaskMethod(Task 3, 3)); var t2 new Taskint(() 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; }
运行结果如下所示需要注意的是如果在ContinueWith()方法中捕获多个任务产生的异常那么它的异常类型是AggregateException具体的异常信息包含在InnerExceptions里面要注意和InnerException区分。 1.9 并行运行任务#
本节中主要介绍了两个方法的使用一个是等待组中全部任务都执行结束的Task.WhenAll()方法另一个是只要组中一个方法执行结束都执行的Task.WhenAny()方法。
具体使用如下演示代码所示。 Copy
static void Main(string[] args) { // 第一种方式 通过Task.WhenAll 等待所有任务运行完成 var firstTask new Taskint(() TaskMethod(First Task, 3)); var secondTask new Taskint(() TaskMethod(Second Task, 2)); // 当firstTask 和 secondTask 运行完成后 才执行 whenAllTask的ContinueWith var whenAllTask Task.WhenAll(firstTask, secondTask); whenAllTask.ContinueWith(t WriteLine($第一个任务答案为{t.Result[0]}第二个任务答案为{t.Result[1]}), TaskContinuationOptions.OnlyOnRanToCompletion); firstTask.Start(); secondTask.Start(); Sleep(TimeSpan.FromSeconds(4)); // 使用WhenAny方法 只要列表中有一个任务完成 那么该方法就会取出那个完成的任务 var tasks new ListTaskint(); for (int i 0; i 4; i) { int counter 1; var task new Taskint(() TaskMethod($Task {counter},counter)); tasks.Add(task); task.Start(); } while (tasks.Count 0) { var completedTask Task.WhenAny(tasks).Result; tasks.Remove(completedTask); WriteLine($一个任务已经完成结果为 {completedTask.Result}); } ReadLine(); } static int TaskMethod(string name, int seconds) { WriteLine($任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程{CurrentThread.IsThreadPoolThread}); Sleep(TimeSpan.FromSeconds(seconds)); return 42 * seconds; }
运行结果如下图所示。 1.10 使用TaskScheduler配置任务执行#
在Task中负责任务调度是TaskScheduler对象FCL提供了两个派生自TaskScheduler的类型线程池任务调度器(Thread Pool Task Scheduler)和同步上下文任务调度器(Synchronization Scheduler)。默认情况下所有应用程序都使用线程池任务调度器但是在UI组件中不使用线程池中的线程避免跨线程更新UI需要使用同步上下文任务调度器。可以通过执行TaskScheduler的FromCurrentSynchronizationContext()静态方法来获得对同步上下文任务调度器的引用。
演示程序如下所示为了延时同步上下文任务调度器我们此次使用WPF来创建项目。
MainWindow.xaml 代码如下所示。 Copy
Window x:ClassRecipe9.MainWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:dhttp://schemas.microsoft.com/expression/blend/2008 xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006 xmlns:localclr-namespace:Recipe9 mc:Ignorabled TitleMainWindow Height450 Width800 Grid TextBlock NameContentTextBlock HorizontalAlignmentLeft Margin44,134,0,0 VerticalAlignmentTop Width425 Height40/ Button ContentSync HorizontalAlignmentLeft Margin45,190,0,0 VerticalAlignmentTop Width75 ClickButtonSync_Click/ Button ContentAsync HorizontalAlignmentLeft Margin165,190,0,0 VerticalAlignmentTop Width75 ClickButtonAsync_Click/ Button ContentAsync OK HorizontalAlignmentLeft Margin285,190,0,0 VerticalAlignmentTop Width75 ClickButtonAsyncOK_Click/ /Grid /Window
MainWindow.xaml.cs 代码如下所示。 Copy
/// summary /// MainWindow.xaml 的交互逻辑 /// /summary public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } // 同步执行 计算密集任务 导致UI线程阻塞 private void ButtonSync_Click(object sender, RoutedEventArgs e) { ContentTextBlock.Text string.Empty; try { string result TaskMethod().Result; ContentTextBlock.Text result; } catch (Exception ex) { ContentTextBlock.Text ex.InnerException.Message; } } // 异步的方式来执行 计算密集任务 UI线程不会阻塞 但是 不能跨线程更新UI 所以会有异常 private void ButtonAsync_Click(object sender, RoutedEventArgs e) { ContentTextBlock.Text string.Empty; Mouse.OverrideCursor Cursors.Wait; Taskstring task TaskMethod(); task.ContinueWith(t { ContentTextBlock.Text t.Exception.InnerException.Message; Mouse.OverrideCursor null; }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext()); } // 通过 异步 和 FromCurrentSynchronizationContext方法 创建了线程同步的上下文 没有跨线程更新UI private void ButtonAsyncOK_Click(object sender, RoutedEventArgs e) { ContentTextBlock.Text string.Empty; Mouse.OverrideCursor Cursors.Wait; Taskstring task TaskMethod(TaskScheduler.FromCurrentSynchronizationContext()); task.ContinueWith(t Mouse.OverrideCursor null, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); } Taskstring TaskMethod() { return TaskMethod(TaskScheduler.Default); } Taskstring TaskMethod(TaskScheduler scheduler) { Task delay Task.Delay(TimeSpan.FromSeconds(5)); return delay.ContinueWith(t { string str $任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程{CurrentThread.IsThreadPoolThread}; Console.WriteLine(str); ContentTextBlock.Text str; return str; }, scheduler); } }
运行结果如下所示从左至右依次单击按钮前两个按钮将会引发异常。
具体信息如下所示。 参考书籍
本文主要参考了以下几本书在此对这些作者表示由衷的感谢感谢你们为.Net的发扬光大所做的贡献 《CLR via C#》《C# in Depth Third Edition》《Essential C# 6.0》《Multithreading with C# Cookbook Second Edition》《C#多线程编程实战》源码下载点击链接 示例源码下载
笔者水平有限如果错误欢迎各位批评指正
本来想趁待业期间的时间读完《Multithreading with C# Cookbook Second Edition》这本书并且分享做的相关笔记但是由于笔者目前职业规划和身体原因可能最近都没有时间来更新这个系列没法做到几天一更。请大家多多谅解但是笔者一定会将这个系列全部更新完成的感谢大家的支持
作者InCerry
出处https://www.cnblogs.com/InCerry/p/9450493.html
版权本文采用「署名 4.0 国际」知识共享许可协议进行许可。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/922939.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!