当前位置: 首页 > news >正文

【微软技术栈】基于任务的异步编程

本文内容

  1. 隐式创建和运行任务
  2. 显式创建和运行任务
  3. 任务 ID
  4. 任务创建选项
  5. 任务、线程和区域性
  6. 创建任务延续
  7. 创建分离的子任务
  8. 创建子任务
  9. 等待任务完成
  10. 组合任务
  11. 处理任务中的异常
  12. 取消任务
  13. TaskFactory 类
  14. 无委托的任务
  15. 自定义计划程序
  16. 相关数据结构
  17. 自定义任务类型

任务并行库 (TPL) 以“任务”的概念为基础,后者表示异步操作。 在某些方面,任务类似于线程或 ThreadPool 工作项,但是抽象级别更高。 术语“任务并行”是指一个或多个独立的任务同时运行。 任务提供两个主要好处:

  • 系统资源的使用效率更高,可伸缩性更好。

    在后台,任务排队到已使用算法增强的 ThreadPool,这些算法能够确定线程数并随之调整。 这些算法提供负载平衡以实现吞吐量最大化。 此进程会使任务相对轻量,你可以创建很多任务以启用细化并行。

  • 对于线程或工作项,可以使用更多的编程控件。

    任务和围绕它们生成的框架提供了一组丰富的 API,这些 API 支持等待、取消、继续、可靠的异常处理、详细状态、自定义计划等功能。

出于这两个原因,在 .NET 中,TPL 是用于编写多线程、异步和并行代码的首选 API。

1、隐式创建和运行任务

Parallel.Invoke 方法提供了一种简便方式,可同时运行任意数量的任意语句。 只需为每个工作项传入 Action 委托即可。 创建这些委托的最简单方式是使用 lambda 表达式。 lambda 表达式可调用指定的方法,或提供内联代码。 下面的示例演示一个基本的 Invoke 调用,该调用创建并启动同时运行的两个任务。 第一个任务由调用名为 DoSomeWork 的方法的 lambda 表达式表示,第二个任务由调用名为 DoSomeOtherWork 的方法的 lambda 表达式表示。

 备注

本文档使用 lambda 表达式在 TPL 中定义委托。 

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());

 备注

Invoke 在后台创建的 Task 实例数不一定与所提供的委托数相等。 TPL 可能会使用各种优化,特别是对于大量的委托。

为了更好地控制任务执行或从任务返回值,必须更加显式地使用 Task 对象。

2、显式创建和运行任务

不返回值的任务由 System.Threading.Tasks.Task 类表示。 返回值的任务由 System.Threading.Tasks.Task<TResult> 类表示,该类从 Task 继承。 任务对象处理基础结构详细信息,并提供可在任务的整个生存期内从调用线程访问的方法和属性。 例如,可以随时访问任务的 Status 属性,以确定它是已开始运行、已完成运行、已取消还是引发了异常。 状态由 TaskStatus 枚举表示。

在创建任务时,你赋予它一个用户委托,该委托封装该任务将执行的代码。 该委托可以表示为命名的委托、匿名方法或 lambda 表达式。 lambda 表达式可以包含对命名方法的调用,如下面的示例所示。 该示例包含对 Task.Wait 方法的调用,以确保任务在控制台模式应用程序结束之前完成执行。

using System;
using System.Threading;
using System.Threading.Tasks;public class Lambda
{public static void Main(){Thread.CurrentThread.Name = "Main";// Create a task and supply a user delegate by using a lambda expression.Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));// Start the task.taskA.Start();// Output a message from the calling thread.Console.WriteLine("Hello from thread '{0}'.",Thread.CurrentThread.Name);taskA.Wait();}
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.

你还可以使用 Task.Run 方法通过一个操作创建并启动任务。 无论是哪个任务计划程序与当前线程关联,Run 方法都将使用默认的任务计划程序来管理任务。 不需要对任务的创建和计划进行更多控制时,首选 Run 方法创建并启动任务。

using System;
using System.Threading;
using System.Threading.Tasks;namespace Run;public class Example
{public static void Main(){Thread.CurrentThread.Name = "Main";// Define and run the task.Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));// Output a message from the calling thread.Console.WriteLine("Hello from thread '{0}'.",Thread.CurrentThread.Name);taskA.Wait();}
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.

你还可以使用 TaskFactory.StartNew 方法在一个操作中创建并启动任务。 如下例所示,可以在以下情况下使用此方法:

  • 无需将创建与计划分开,且需要额外的任务创建选项或需要使用特定的计划程序。

  • 需要将其他状态传递给可通过其 Task.AsyncState 属性检索的任务。

using System;
using System.Threading;
using System.Threading.Tasks;namespace TaskIntro;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class AsyncState
{public static void Main(){Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++){taskArray[i] = Task.Factory.StartNew((Object obj) =>{CustomData data = obj as CustomData;if (data == null) return;data.ThreadNum = Thread.CurrentThread.ManagedThreadId;},new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });}Task.WaitAll(taskArray);foreach (var task in taskArray){var data = task.AsyncState as CustomData;if (data != null)Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);}}
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.

Task 和 Task<TResult> 均公开静态 Factory 属性,该属性返回 TaskFactory 的默认实例,因此你可以调用该方法为 Task.Factory.StartNew()。 此外,在以下示例中,由于任务的类型为 System.Threading.Tasks.Task<TResult>,因此每个任务都具有包含计算结果的公共 Task<TResult>.Result 属性。 任务以异步方式运行,可以按任意顺序完成。 如果在计算完成之前访问 Result 属性,则该属性将阻止调用线程。

使用 lambda 表达式创建委托时,你有权访问源代码中当时可见的所有变量。 然而,在某些情况下,特别是在循环中,lambda 不按照预期的方式捕获变量。 它仅捕获变量的引用,而不是它每次迭代后更改的值。 以下示例演示了该问题。 它将循环计数器传递给实例化 CustomData 对象并使用循环计数器作为对象标识符的 lambda 表达式。 如示例输出所示,每个 CustomData 对象都具有相同的标识符。

using System;
using System.Threading;
using System.Threading.Tasks;namespace Example.Iterations;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class IterationTwo
{public static void Main(){// Create the task object by using an Action(Of Object) to pass in the loop// counter. This produces an unexpected result.Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++) {taskArray[i] = Task.Factory.StartNew( (Object obj) => {var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};data.ThreadNum = Thread.CurrentThread.ManagedThreadId;Console.WriteLine("Task #{0} created at {1} on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);},i );}Task.WaitAll(taskArray);}
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.

通过使用构造函数向任务提供状态对象,可以在每次迭代时访问该值。 以下示例在上一示例的基础上做了修改,在创建 CustomData 对象时使用循环计数器,该对象继而传递给 lambda 表达式。 如示例输出所示,每个 CustomData 对象现在都具有唯一的一个标识符,该标识符基于该对象实例化时循环计数器的值。

using System;
using System.Threading;
using System.Threading.Tasks;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class IterationOne
{public static void Main(){// Create the task object by using an Action(Of Object) to pass in custom data// to the Task constructor. This is useful when you need to capture outer variables// from within a loop.Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++) {taskArray[i] = Task.Factory.StartNew( (Object obj ) => {CustomData data = obj as CustomData;if (data == null)return;data.ThreadNum = Thread.CurrentThread.ManagedThreadId;Console.WriteLine("Task #{0} created at {1} on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);},new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );}Task.WaitAll(taskArray);}
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.

此状态作为参数传递给任务委托,并且可通过使用 Task.AsyncState 属性从任务对象访问。 以下示例在上一示例的基础上演变而来。 它使用 AsyncState 属性显示关于传递到 lambda 表达式的 CustomData 对象的信息。

using System;
using System.Threading;
using System.Threading.Tasks;namespace TaskIntro;class CustomData
{public long CreationTime;public int Name;public int ThreadNum;
}public class AsyncState
{public static void Main(){Task[] taskArray = new Task[10];for (int i = 0; i < taskArray.Length; i++){taskArray[i] = Task.Factory.StartNew((Object obj) =>{CustomData data = obj as CustomData;if (data == null) return;data.ThreadNum = Thread.CurrentThread.ManagedThreadId;},new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });}Task.WaitAll(taskArray);foreach (var task in taskArray){var data = task.AsyncState as CustomData;if (data != null)Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",data.Name, data.CreationTime, data.ThreadNum);}}
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.

3、任务 ID

每个任务都获得一个在应用程序域中唯一标识自己的整数 ID,可以使用 Task.Id 属性访问该 ID。 该 ID 可有效用于在 Visual Studio 调试器的“并行堆栈”和“任务”窗口中查看任务信息。 该 ID 是以惰性方式创建的,这意味着请求该 ID 时才会创建该 ID。 因此,每次运行程序时,任务可能具有不同的 ID。 

4、任务创建选项

创建任务的大多数 API 提供接受 TaskCreationOptions 参数的重载。 通过指定下列某个或多个选项,可指示任务计划程序在线程池中安排任务计划的方式。 可以使用位 OR 运算组合选项。

下面的示例演示一个具有 LongRunning 和 PreferFairness 选项的任务:

var task3 = new Task(() => MyLongRunningMethod(),TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

5、任务、线程和区域性

每个线程都具有一个关联的区域性和 UI 区域性,分别由 Thread.CurrentCulture 和 Thread.CurrentUICulture 属性定义。 线程的区域性用在格式设置、分析、排序和字符串比较等操作中。 线程的 UI 区域性用于查找资源。

系统区域性定义线程的默认区域性和 UI 区域性。 但你可以使用 CultureInfo.DefaultThreadCurrentCulture 和 CultureInfo.DefaultThreadCurrentUICulture 属性为应用程序域中的所有线程指定默认区域性。 如果你显式设置线程的区域性并启动新线程,则新线程不会继承正在调用的线程的区域性;相反,其区域性就是默认系统区域性。 但是,在基于任务的编程中,任务使用调用线程的区域性,即使任务在不同线程上以异步方式运行也是如此。

下面的示例提供了简单的演示。 它将应用的当前区域性更改为法语(法国)。 如果法语(法国)已经是当前区域性,它会更改为英语(美国)。 然后,调用一个名为 formatDelegate 的委托,该委托返回在新区域性中格式化为货币值的数字。 无论委托是由任务同步调用还是异步调用,该任务都将使用调用线程的区域性。

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;public class Example
{public static void Main(){decimal[] values = { 163025412.32m, 18905365.59m };string formatString = "C2";Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",CultureInfo.CurrentCulture.Name,Thread.CurrentThread.ManagedThreadId);foreach (var value in values)output += String.Format("{0}   ", value.ToString(formatString));output += Environment.NewLine;return output;};Console.WriteLine("The example is running on thread {0}",Thread.CurrentThread.ManagedThreadId);// Make the current culture different from the system culture.Console.WriteLine("The current culture is {0}",CultureInfo.CurrentCulture.Name);if (CultureInfo.CurrentCulture.Name == "fr-FR")Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");elseThread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");Console.WriteLine("Changed the current culture to {0}.\n",CultureInfo.CurrentCulture.Name);// Execute the delegate synchronously.Console.WriteLine("Executing the delegate synchronously:");Console.WriteLine(formatDelegate());// Call an async delegate to format the values using one format string.Console.WriteLine("Executing a task asynchronously:");var t1 = Task.Run(formatDelegate);Console.WriteLine(t1.Result);Console.WriteLine("Executing a task synchronously:");var t2 = new Task<String>(formatDelegate);t2.RunSynchronously();Console.WriteLine(t2.Result);}
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €

在 .NET Framework 4.6 之前的 .NET Framework 版本中,任务的区域性由它在其上运行的线程区域性确定,而不是调用线程的区域性。 对于异步任务,任务使用的区域性可能不同于调用线程的区域性。

有关异步任务和区域性的详细信息,请参阅 CultureInfo 一文中的“区域性和基于异步任务的操作”部分。

6、创建任务延续

使用 Task.ContinueWith 和 Task<TResult>.ContinueWith 方法,可以指定要在先行任务完成时启动的任务。 延续任务的委托被传递给对先行任务的引用,以便它查看先行任务的状态。 通过检索 Task<TResult>.Result 属性的值,可以将先行任务的输出用作延续任务的输入。

在下面的示例中,getData 任务通过调用 TaskFactory.StartNew<TResult>(Func<TResult>) 方法来启动。 当 processData 完成时,getData 任务自动启动,当 displayData 完成时,processData 启动。 getData 产生一个整数数组,通过 processData 任务的 getData 属性,Task<TResult>.Result 任务可访问该数组。 processData 任务处理该数组并返回结果,结果的类型从传递到 Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) 方法的 Lambda 表达式的返回类型推断而来。 displayData 完成时,processData 任务自动执行,而 Tuple<T1,T2,T3> 任务可通过 processData 任务的 displayData 属性访问由 processData lambda 表达式返回的 Task<TResult>.Result 对象。 displayData 任务采用 processData 任务的结果。 它得出自己的结果,其类型以相似方式推断而来,且可由程序中的 Result 属性使用。

using System;
using System.Threading.Tasks;public class ContinuationOne
{public static void Main(){var getData = Task.Factory.StartNew(() => {Random rnd = new Random();int[] values = new int[100];for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)values[ctr] = rnd.Next();return values;} );var processData = getData.ContinueWith((x) => {int n = x.Result.Length;long sum = 0;double mean;for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)sum += x.Result[ctr];mean = sum / (double) n;return Tuple.Create(n, sum, mean);} );var displayData = processData.ContinueWith((x) => {return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",x.Result.Item1, x.Result.Item2,x.Result.Item3);} );Console.WriteLine(displayData.Result);}
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

因为 Task.ContinueWith 是实例方法,所以你可以将方法调用链接在一起,而不是为每个先行任务去实例化 Task<TResult> 对象。 以下示例与上一示例在功能上等同,唯一的不同在于它将对 Task.ContinueWith 方法的调用链接在一起。 通过方法调用链返回的 Task<TResult> 对象是最终延续任务。

using System;
using System.Threading.Tasks;public class ContinuationTwo
{public static void Main(){var displayData = Task.Factory.StartNew(() => {Random rnd = new Random();int[] values = new int[100];for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)values[ctr] = rnd.Next();return values;} ).ContinueWith((x) => {int n = x.Result.Length;long sum = 0;double mean;for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)sum += x.Result[ctr];mean = sum / (double) n;return Tuple.Create(n, sum, mean);} ).ContinueWith((x) => {return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",x.Result.Item1, x.Result.Item2,x.Result.Item3);} );Console.WriteLine(displayData.Result);}
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

使用 ContinueWhenAll 和 ContinueWhenAny 方法,可以从多个任务继续。

7、创建分离的子任务

如果在任务中运行的用户代码创建一个新任务,且未指定 AttachedToParent 选项,则该新任务不采用任何特殊方式与父任务同步。 这种不同步的任务类型称为“分离的嵌套任务”或“分离的子任务”。 以下示例展示了创建一个分离子任务的任务:

var outer = Task.Factory.StartNew(() =>
{Console.WriteLine("Outer task beginning.");var child = Task.Factory.StartNew(() =>{Thread.SpinWait(5000000);Console.WriteLine("Detached task completed.");});
});outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.

 备注

父任务不会等待分离子任务完成。

8、创建子任务

如果任务中运行的用户代码在创建任务时指定了 AttachedToParent 选项,新任务就称为父任务的附加子任务。 因为父任务隐式地等待所有附加子任务完成,所以你可以使用 AttachedToParent 选项表示结构化的任务并行。 以下示例展示了创建 10 个附加子任务的父任务。 该示例调用 Task.Wait 方法来等待父任务完成。 它不必显式等待附加子任务完成。

using System;
using System.Threading;
using System.Threading.Tasks;public class Child
{public static void Main(){var parent = Task.Factory.StartNew(() => {Console.WriteLine("Parent task beginning.");for (int ctr = 0; ctr < 10; ctr++) {int taskNo = ctr;Task.Factory.StartNew((x) => {Thread.SpinWait(5000000);Console.WriteLine("Attached child #{0} completed.",x);},taskNo, TaskCreationOptions.AttachedToParent);}});parent.Wait();Console.WriteLine("Parent task completed.");}
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.

父任务可使用 TaskCreationOptions.DenyChildAttach 选项阻止其他任务附加到父任务。 

9、等待任务完成

System.Threading.Tasks.Task 和 System.Threading.Tasks.Task<TResult> 类型提供了 Task.Wait 方法的若干重载,以便能够等待任务完成。 此外,使用静态 Task.WaitAll 和 Task.WaitAny 方法的重载可以等待一批任务中的任一任务或所有任务完成。

通常,会出于以下某个原因等待任务:

  • 主线程依赖于任务计算的最终结果。

  • 你必须处理可能从任务引发的异常。

  • 应用程序可以在所有任务执行完毕之前终止。 例如,执行 Main(应用程序入口点)中的所有同步代码后,控制台应用程序将终止。

下面的示例演示不包含异常处理的基本模式:

Task[] tasks = new Task[3]
{Task.Factory.StartNew(() => MethodA()),Task.Factory.StartNew(() => MethodB()),Task.Factory.StartNew(() => MethodC())
};//Block until all tasks complete.
Task.WaitAll(tasks);// Continue on this thread...

某些重载允许你指定超时,而其他重载采用额外的 CancellationToken 作为输入参数,以便可以通过编程方式或根据用户输入来取消等待。

等待任务时,其实是在隐式等待使用 TaskCreationOptions.AttachedToParent 选项创建的该任务的所有子级。 Task.Wait 在该任务已完成时立即返回。 Task.Wait 方法将抛出由某任务引发的任何异常,即使 Task.Wait 方法是在该任务完成之后调用的。

10、组合任务

Task 和 Task<TResult> 类提供了多种方法来帮助组合多个任务。 这些方法实现常用模式并更好地利用 C#、Visual Basic 和 F# 提供的异步语言功能。 本节介绍了 WhenAll、WhenAny、Delay 和 FromResult 方法。

Task.WhenAll

Task.WhenAll 方法异步等待多个 Task 或 Task<TResult> 对象完成。 通过它提供的重载版本可以等待非均匀任务组。 例如,你可以等待多个 Task 和 Task<TResult> 对象在一个方法调用中完成。

Task.WhenAny

Task.WhenAny 方法异步等待多个 Task 或 Task<TResult> 对象中的一个完成。 与在 Task.WhenAll 方法中一样,该方法提供重载版本,让你能等待非均匀任务组。 WhenAny 方法在下列情境中尤其有用:

  • 冗余运算:请考虑可以用多种方式执行的算法或运算。 你可使用 WhenAny 方法来选择先完成的运算,然后取消剩余的运算。

  • 交错运算:你可启动必须完成的多项运算,并使用 WhenAny 方法在每项运算完成时处理结果。 在一项运算完成后,可以启动一个或多个任务。

  • 限制运算:你可使用 WhenAny 方法通过限制并发运算的数量来扩展前面的情境。

  • 到期运算:你可使用 WhenAny 方法在一个或多个任务与特定时间后完成的任务(例如 Delay 方法返回的任务)间进行选择。 下节描述了 Delay 方法。

Task.Delay

Task.Delay 方法将生成在指定时间后完成的 Task 对象。 你可以使用此方法生成用于轮询数据的循环,指定超时,延迟处理用户输入等。

Task(T).FromResult

通过使用 Task.FromResult 方法,你可以创建包含预计算结果的 Task<TResult> 对象。 执行返回 Task<TResult> 对象的异步运算,且已计算该 Task<TResult> 对象的结果时,此方法将十分有用。

11、处理任务中的异常

当某个任务抛出一个或多个异常时,异常包装在 AggregateException 异常中。 该异常会传播回与任务联接的线程。 通常,该线程是等待任务完成的线程或访问 Result 属性的线程。 此行为强制实施 .NET Framework 策略 - 默认所有未处理的异常应终止进程。 调用代码可以通过使用 try/catch 块中的以下任意方法来处理异常:

  • Wait 方法

  • WaitAll 方法

  • WaitAny 方法

  • Result 属性

联接线程也可以通过在对任务进行垃圾回收之前访问 Exception 属性来处理异常。 通过访问此属性,可防止未处理的异常在对象完成时触发终止进程的异常传播行为。

12、取消任务

Task 类支持协作取消,并与 .NET Framework 4 中新增的 System.Threading.CancellationTokenSource 类和 System.Threading.CancellationToken 类完全集成。 System.Threading.Tasks.Task 类中的大多数构造函数采用 CancellationToken 对象作为输入参数。 许多 StartNew 和 Run 重载还包括 CancellationToken 参数。

你可以创建标记,并使用 CancellationTokenSource 类在以后某一时间发出取消请求。 可以将该标记作为参数传递给 Task,还可以在执行响应取消请求的工作的用户委托中引用同一标记。

13、TaskFactory 类

TaskFactory 类提供静态方法,这些方法封装了用于创建和启动任务和延续任务的常用模式。

  • 最常用模式为 StartNew,它在一个语句中创建并启动任务。

  • 如果通过多个先行任务创建延续任务,请使用 ContinueWhenAll 方法或 ContinueWhenAny 方法,或它们在 Task<TResult> 类中的相当方法。。

  • 若要在 BeginX 或 EndX 实例中封装异步编程模型 Task 和 Task<TResult> 方法,请使用 FromAsync 方法。

默认的 TaskFactory 可作为 Task 类或 Task<TResult> 类上的静态属性访问。 你还可以直接实例化 TaskFactory 并指定各种选项,包括 CancellationToken、TaskCreationOptions 选项、TaskContinuationOptions 选项或 TaskScheduler。 创建任务工厂时所指定的任何选项将应用于它创建的所有任务,除非 Task 是通过使用 TaskCreationOptions 枚举创建的(在这种情况下,任务的选项重写任务工厂的选项)。

14、无委托的任务

在某些情况下,可能需要使用 Task 封装由外部组件(而不是用户委托)执行的某个异步操作。 如果该操作基于异步编程模型 Begin/End 模式,你可以使用 FromAsync 方法。 如果不是这种情况,你可以使用 TaskCompletionSource<TResult> 对象将该操作包装在任务中,并因而获得 Task 可编程性的一些好处。 例如对异常传播和延续的支持。 

15、自定义计划程序

大多数应用程序或库开发人员并不关心任务在哪个处理器上运行、任务如何将其工作与其他任务同步以及如何在 System.Threading.ThreadPool 中计划任务。 他们只需要它在主机上尽可能高效地执行。 如果需要对计划细节进行更细化的控制,可以使用 TPL 在默认任务计划程序上配置一些设置,甚至是提供自定义计划程序。 

16、相关数据结构

TPL 有几种在并行和顺序方案中都有用的新公共类型。 其中包括 System.Collections.Concurrent 命名空间中的多个线程安全、快速且可缩放的集合类,以及多个新的同步类型。 例如 System.Threading.Semaphore 和 System.Threading.ManualResetEventSlim,对于特定类型的工作负载,两者在效率方面超过了原有类型。 .NET Framework 4 中的其他新类型(例如 System.Threading.Barrier 和 System.Threading.SpinLock)提供了早期版本中未提供的功能。 

17、自定义任务类型

建议不要从 System.Threading.Tasks.Task 或 System.Threading.Tasks.Task<TResult> 继承。 相反,我们建议你使用 AsyncState 属性将其他数据或状态与 Task 或 Task<TResult> 对象相关联。 还可以使用扩展方法扩展 Task 和 Task<TResult> 类的功能。 有关扩展方法的详细信息,请参阅扩展方法和扩展方法。

如果必须从 Task 或 Task<TResult> 继承,则不能使用 Run 或 System.Threading.Tasks.TaskFactory,System.Threading.Tasks.TaskFactory<TResult> 或 System.Threading.Tasks.TaskCompletionSource<TResult> 类创建自定义任务类型的实例。 不能使用是因为这些类仅创建 Task 和 Task<TResult> 对象。 此外,不能使用 Task、Task<TResult>、TaskFactory 和 TaskFactory<TResult> 提供的任务延续机制创建自定义任务类型的实例。 不能使用也是因为这些类仅创建 Task 和 Task<TResult> 对象。

相关文章:

【微软技术栈】基于任务的异步编程

本文内容 隐式创建和运行任务显式创建和运行任务任务 ID任务创建选项任务、线程和区域性创建任务延续创建分离的子任务创建子任务等待任务完成组合任务处理任务中的异常取消任务TaskFactory 类无委托的任务自定义计划程序相关数据结构自定义任务类型 任务并行库 (TPL) 以“任…...

react hooks 学习之react router v6 路由表配置

/ 如果你是在ts中&#xff0c;那么这个这个文件名是router.ts那么这个<Home/>这里会报eslint错误&#xff0c;所以为了解决这个错误&#xff0c;直接改成router.tsx就行 import { RouteObject } from react-router-dom; import Home from ../page/Home; import About fr…...

Echarts 设置数据条颜色 宽度

设置数据条颜色&#xff08;推荐&#xff09; let yData [{value: 500,time: 2012-11-12,itemStyle: //设置数据条颜色{normal: { color: red }}},{value: 454,time: 2020-5-17},{value: 544,time: 2022-1-22},{value: 877,time: 2013-1-30}, {value: 877,time: 2012-11-12}]…...

2023-11-30 通过中缀表达式转换后缀表达式, 用C语言完成一个简单的计算器

点击 <C 语言编程核心突破> 快速C语言入门 通过中缀表达式转换后缀表达式, 用C语言完成一个简单的计算器 前言一、中缀表达式和后缀表达式 (AI辅助)二、中缀转后缀规则及后缀运算规则 (AI辅助)总结 前言 要解决问题: 在练习用Qt完成一个简单的计算器时, 需要将一个文本…...

设计模式总目录

目录 设计模式 1. 创建型模式 1.1 工厂方法模式 1.2 抽象工厂模式 1.3 单例模式 1.4 建造者模式 1.5原型模式 2. 结构型模式 2.1 适配器模式 2.2 装饰器模式 2.3 代理模式 2.4 外观模式 2.5 桥接模式 2.6 组合模式 2.7 享元模式 3. 行为型模式 3.1 策略模式 …...

通俗理解词向量模型,预训练模型,Transfomer,Bert和GPT的发展脉络和如何实践

最近研究GPT&#xff0c;深入的从transfomer的原理和代码看来一下&#xff0c;现在把学习的资料和自己的理解整理一下。 这个文章写的很通俗易懂&#xff0c;把transformer的来龙去脉&#xff0c;还举例了很多不错的例子。 Transformer通俗笔记&#xff1a;从Word2Vec、Seq2S…...

键入网址到网页显示,期间发生了什么?(计算机网络)

浏览器首先会对URL进行解析 下面以http://www.server.com/dir1/file1.html为例 当没有路径名时&#xff0c;就代表访问根目录下事先设置的默认文件&#xff0c;也就是 /index.html 或者 /default.html 对URL进行解析之后&#xff0c;浏览器确定了 Web 服务器和文件名&#x…...

python-GC机制、装饰器、生成器、迭代器、三元表达式、列表生成式、生成器表达式、函数递归、面向对象、

1 基础知识 1.1 GC机制 Python的垃圾回收&#xff0c;其实高级的语言都有自己的垃圾回收机制简称GC&#xff0c; python当中主要通过三种方式解决垃圾回收的方式&#xff0c;引用计数、标记清除、分代回收。引用计数&#xff1a;如果有新的引用指向对象&#xff0c;对象引用计…...

Linux命令--根据端口号查看进程号(PID)

Linux命令–根据端口号查看进程号&#xff08;PID&#xff09; 查找8080端口对应的进程号: netstat -nlp|grep :8297对应的进程号1061,如果想杀掉此进程&#xff0c;可以用一下命令&#xff1a; kill -9 1061...

LangChain 9 模型Model I/O 聊天提示词ChatPromptTemplate, 少量样本提示词FewShotPrompt

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…...

使用 Vue3 + Pinia + Ant Design Vue3 搭建后台管理系统

Vue3 & Ant Design Vue3基础 nodejs版本要求&#xff1a;node-v18.16.0-x64 nodejs基础配置 npm -v node -vnpm config set prefix "D:\software\nodejs\node_global" npm config set cache "D:\software\nodejs\node_cache"npm config get registry …...

SpringCloud核心组件

Eureka 注册中心&#xff0c;服务的注册与发现 Feign远程调用 Ribbon负载均衡&#xff0c;默认轮询 Hystrix 熔断 降级 Zuul微服务网关&#xff08;这个组件负责网络路由&#xff0c;可以做统一的降级、限流、认证授权、安全&#xff09; Eureka 微服务的功能主要有以下几…...

基于C++11实现将IP地址、端口号和连接状态写入文件

要基于C11实现将IP地址、端口号和连接状态写入文件&#xff0c;您可以使用std::ofstream类来打开文件并进行写入操作。以下是一个示例&#xff1a; #include <iostream> #include <fstream>void writeConnectionStatus(const std::string& ip, int port, bool…...

非空断言,

先看下TypeScript基础之非空断言操作符、可选链运算符、空值合并运算符-CSDN博客 我没有复现出来&#xff0c;但是我知道了它的作用 用 let str: string arg!; 代替 let str: string; if (arg) { str arg; } 非空断言&#xff08;!&#xff09;和不使用的区别在于对于…...

Spark---创建DataFrame的方式

1、读取json格式的文件创建DataFrame 注意&#xff1a; 1、可以两种方式读取json格式的文件。 2、df.show()默认显示前20行数据。 3、DataFrame原生API可以操作DataFrame。 4、注册成临时表时&#xff0c;表中的列默认按ascii顺序显示列。 df.createTempView("mytab…...

瑜伽学习零基础入门,各种瑜伽教学方法全集

一、教程描述 练习瑜伽的好处多多&#xff0c;能够保证平衡健康的身体基础&#xff0c;提升气质、塑造形体、陶冶情操&#xff0c;等等。本套教程是瑜伽的组合教程&#xff0c;共由33套视频教程组合而成&#xff0c;包含了塑身纤体&#xff0c;速效瘦身&#xff0c;四季养生&a…...

pycharm编译报错处理

1.c生成工具下载 https://visualstudio.microsoft.com/visual-cpp-build-tools/ 在这里插入图片描述 pip install pycocotools...

“华为杯”研究生数学建模竞赛2019年-【华为杯】E题:基于多变量的全球气候与极端天气模型的构建与应用(附python代码实现)

目录 摘 要: 一.问题重述 1.1 问题背景 1.2 问题提出 二.模型假设及符号设定...

冒泡排序(适合编程新手的体质)

冒泡排序&#xff1a;简单而高效的排序技巧 欢迎来到我们今天的博客&#xff0c;我们将一起探索计算机科学中最基本但同时也非常重要的概念之一&#xff1a;冒泡排序。无论你是编程新手还是有一些编程经验的读者&#xff0c;这篇博客都将帮助你更好地理解冒泡排序的原理和应用…...

pdfjs,pdf懒加载

PDF.js是一个使用JavaScript实现的PDF阅读器&#xff0c;它可以在Web浏览器中显示PDF文档。PDF.js支持懒加载&#xff0c;也就是说&#xff0c;它可以在用户滚动页面时才加载PDF文档的某些部分&#xff0c;从而减少初始加载时间和内存占用。 注意点&#xff1a;如果要运行在多留…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...

flow_controllers

关键点&#xff1a; 流控制器类型&#xff1a; 同步&#xff08;Sync&#xff09;&#xff1a;发布操作会阻塞&#xff0c;直到数据被确认发送。异步&#xff08;Async&#xff09;&#xff1a;发布操作非阻塞&#xff0c;数据发送由后台线程处理。纯同步&#xff08;PureSync…...

Java设计模式:责任链模式

一、什么是责任链模式&#xff1f; 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09; 是一种 行为型设计模式&#xff0c;它通过将请求沿着一条处理链传递&#xff0c;直到某个对象处理它为止。这种模式的核心思想是 解耦请求的发送者和接收者&#xff0c;…...

持续交付的进化:从DevOps到AI驱动的IT新动能

文章目录 一、持续交付的本质&#xff1a;从手动到自动的交付飞跃关键特性案例&#xff1a;电商平台的高效部署 二、持续交付的演进&#xff1a;从CI到AI驱动的未来发展历程 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/101f72defaf3493ba0ba376bf09367a2.png)中国…...