C# 6.0本质论(多线程处理)

十八、多线程处理

18.1 多线程概述

18.1.1 延迟产生的原因

  • 计算处理耗时
  • 获取结果耗时,如网络、外部设备

18.1.2 多线程的作用

  • 实现多任务
    • 在一个进程中进行多任务处理
  • 解决延迟

18.1.3 多线程处理的方式

  • 多CPU处理多线程
    • 最理想的情况下,一个线程单独占用一个CPU,完成一个任务的时间大大缩短
  • 单CPU处理多线程
    • 时间分片
      • 一个处理器处理多个线程时,操作系统以极快的速度从一个线程切换到另一个线程,模拟多个线程并发运行
    • 上下文切换
      • 指在同一个内核中切换线程
      • 上下文切换会影响性能
        • 切换前需要将内部状态保存到内存,并加载与新线程有关的状态
      • 总耗时不会降低

18.1.4 线程处理的问题

18.1.4.1 操作的非原子性
  • 可能出现部分操作完成的情况,造成状态不一致
18.1.4.2 竞态的不确定性
  • 多线程抢夺CPU时间,可能某个线程一直占用
18.1.4.3 内存模型的复杂性
  • CPU在访问变量时不会每次都访问主内存,而是在各自处理器的高速缓存中生成变量副本,副本会定时与主内存同步。
18.1.4.4 锁定造成死锁
  • 锁可以解决非原子性操作、竞态条件、高速缓存同步问题
  • 但如果不同线程以不同顺序获取锁可能会造成死锁

18.2 使用System.Threading

  • 直接操作线程的API

18.2.1 使用System.Threading.Thread进行异步操作

  • 操作系统实现线程并提供各种非托管API来创建和管理线程
  • CLR封装这些非托管代码,并在托管代码中通过System.Threading.Thread类来公开它们。
18.2.1.1 ThreadStart
  • 相当于runnable,委托类型,接收要在线程中运行的方法
    • public delegate void ThreadStart()
18.2.1.2 ParameterizedThreadStart
  • 接收一个参数
    • public delegate void ParameterizedThreadStart(object obj)
public static void Main()
{
	ThreadStart threadStart = DoWork;
	Thread thread = new Thread(threadStart);
	thread.Start();
	for(int count = 0; count< Repetitions; count++)
	{
		Console.Write('-');	
	}
	thread.Join();
}
public static void DoWork()
{
	for(int count = 0; count< Repetitions; count++)
	{
		Console.Write('+');	
	}
}

18.2.2 线程管理

  • Join()
    • 阻塞调用者线程,直到该实例表示的线程终止
      • 调用者线程
        • 在某个线程中调用thread.join()方法的那个线程
        • 一般为主线程
      • 实例表示的线程
        • 即thread.join()中thread实例表示的线程
  • IsBackGround
    • thread实例属性
      • 可显式将线程标记为后台线程
        • thread.IsBackGround = true
      • 新线程默认为前台线程
    • 所有前台线程完成后会终止进程
  • Priority
    • thread实例属性
    • 可以修改线程的优先级,ThreadPriority枚举值
      • 优先级高的线程获取时间片的几率更大
  • ThreadState
    • thread实例属性
    • 线程状态

18.2.3 在生产代码中不要让线程进入睡眠

  • Sleep()
    • Thread类的静态方法
    • 使当前线程进入睡眠
      • 告诉系统在指定时间内不要为该线程调度任何时间片
      • 当前线程发生阻塞,不会被回收,但什么也不做
    • 避免使用
      • 睡眠时间不保证
      • 浪费线程昂贵资源
    • 用处
      • 使用方式一:将睡眠时间设为0
        • 主动放弃本次对CPU的占用,线程正常调度,重新进入抢夺资源的状态
      • 使用方式二:将睡眠时间设置为永久
        • 测试模拟高延迟操作,不必让处理器真正去做无意义的运算

18.2.4 在生产代码中不要中止线程

  • Abort()
    • 避免使用,会引发异常
    • 在finally块或非托管代码中会推迟执行

18.2.5 线程池处理

  • 线程池
    • 减少新建线程的耗时
    • 线程池的使用
      • ThreadPool.QuenueUserWorkItem(WaitCallback callBack, object state)
        • WaitCallback委托类型
        • delegate void WaitCallback(object state)
    • 缺点
      • 有线程数量上限
      • 无法获得工作线程的引用从而控制线程
      • 长时间的耗时任务会长时间占用线程,而短时间的耗时任务可能在排队
public static void Main()
{
	ThreadPool.QuenueUserWorkItem(DoWork)
	for(int count = 0; count< Repetitions; count++)
	{
		Console.Write('-');	
	}
	Thread.Sleep(1000);
}
public static void DoWork()
{
	for(int count = 0; count< Repetitions; count++)
	{
		Console.Write('+');	
	}
}

18.3 异步任务

  • TPL ( Task Parallel Library )
    • 并行任务库
  • TPL工作模式
    • 在 .net Framework 4 中TPL会先创建一个Task,并告知任务调度器
    • 任务调度器采取多种策略完成任务
      • 默认是向线程池请求线程。
    • 线程池也会自己判断如何提供线程
      • 例如,等当前任务执行完再运行新任务或是创建全新线程等。

18.3.1 调用任务

18.3.1.1 Task.Run
  • 任务类型
    • 无返回值的任务
      • Task.Run(Action action)
    • 有返回值的任务
      • Task.Run<TResult>(Func<TResult> function)
public static void Main()
{
	Task task = Task.Run(DoWork)
	for(int count = 0; count< Repetitions; count++)
	{
		Console.Write('-');	
	}
	task.Wait();
}
public static void DoWork()
{
	for(int count = 0; count< Repetitions; count++)
	{
		Console.Write('+');	
	}
}
18.3.1.2 Task.Wait
  • task.Wait()
    • 阻塞调用者线程,直到任务完成

Wait is a synchronization method that causes the calling thread to wait until the current task has completed. If the current task has not started execution, the Wait method attempts to remove the task from the scheduler and execute it inline on the current thread. If it is unable to do that, or if the current task has already started execution, it blocks the calling thread until the task completes.

  • task.Wait(int32)
    • 指定时间内,若任务完成,则返回true
    • 否则,返回false
18.3.1.3 任务属性
  • task.IsCompleted
    • 正常结束或异常终止都会返回true
  • Task.CurrentId
    • 静态属性,返回调用Task.CurrentId的那个任务的标识符
  • task.AsyncState
  • task.Result
    • 会自动阻塞当前线程等待返回结果

18.3.2 任务延续

  • ContinueWith( ( task )=>{ … } )
    • 方法接收委托类型作为参数
      • Lambda表达式接收Task作为实参,以便访问先驱任务的完成状态
    • 最终返回Task对象
    • 可设置TaskContinuationOptions值,根据完成状态执行任务
      • 根据选项的不同可以模拟条件触发的事件处理程序
      • OnlyOnRanToCompletion
      • OnlyOnFaluted
      • OnlyOnCanceled
      • 互斥选项当一个任务执行时,任务调度器会取消其他任务,并将其他任务的状态设为canceled,在该任务上调用wait()或其他等待任务完成的方法时会引发异常

18.3.3 用AggregateException处理task上的未处理异常

  • 可以用try/catch块包装任务的委托主体,以捕获异常
    • 捕获能处理的异常
  • 可能存在未被捕获的异常
    • 任何未处理的异常会造成应用程序终止。
  • 异步任务中未处理异常的处理
    • 任务调度器会自动使用一个“catchall”异常处理程序包装委托
      • 保证所有异常都能被捕获
      • 此时,任务处于取消状态
    • 在已取消的任务上调用wait()或其他等待任务完成的方法时会引发AggregateException集合异常
      • 集合的由来
        • 多个异步任务,引发多个异常
        • 异步任务中引发多个异常
        • 按一个异常来进行报告
  • 处理出错任务的方式
    • 任务延续处理异常
      • 利用任务延续时的条件设置错误处理程序来处理异常
    • AggregateException的实例方法
      • Handle()方法
        • 接收委托类型
        • 用于为每个异常设置处理方式
      • Task.Exception属性可以获取AggregateException异常对象
public static void Main()
{
	bool parentTaskFaulted = false;
	Task task = new Task(()=>
		{
			throw new InvalidOperationException();
		});
	Task continuationTask = task.ContinueWith(
		(antecedentTask)=>
		{
			parentTaskFaulted = antecedentTask.IsFaulted;
		},TaskContinuationOptions.OnlyOnFaulted);
	task.Start();
	continuationTask.Wait();
	task.Exception.Handle(eachException =>
	{
		Console.WriteLine($"Error:{eachException.Message}");
		return true;
	});
}
  • 处理Thread上的未处理异常
    • AppDomain
      • 操作系统创建进程耗时、耗资源
      • .net 支持在同一进程中创建多个AppDomain以提供进程间的隔离级别
      • 一般一个应用程序集对应一个AppDomain
    • 未处理异常会导致应用程序中止,但在异常中止前应将数据保存起来
      • AppDomain提供一种机制,所有未处理异常会触发UnhandledException事件,可在该事件中处理数据保存问题
      • try/finally不一定能在未处理异常导致应用程序中止前执行finally

18.4 取消任务

18.4.1 TPL使用协作式取消

  • 不是野蛮的中止正在执行的task,而是将正在执行的部分工作完成后安全的关闭任务
public static void Main()
{
	CansellationTokenSource cancellationTokenSource =
		new CancellationTokenSource();
	Task task = Task.Run(
		()=>WirtePi(cancellationTokenSource.Token)
		, cancellationTokenSource.Token
	);
	Console.ReadLine();
	cancellationTokenSource.Cancel();
	task.Wait();
}
  • CancellationTokenSource
    • 作用
      • 提供标志
      • 并在取消时发出通知。提供线程安全的取消方式
    • 属性和方法
      • Token属性
        • 类型为CancellationToken,传递给Task
      • Cancel()
        • 发出取消通知,默认不抛出异常
        • 任务取消时的状态是RanToCompletion
  • CancellationToken
    • 结构,值类型
    • IsCancellationRequested
      • 判断是否有取消任务的请求
    • Register()
      • 可注册委托方法,监听取消事件并执行委托的相应操作
    • ThrowIfCancellationRequested()
      • 抛出TaskCanceledException,派生自OperationCanceledException,用于报告任务取消状态,此时任务状态为Canceled

18.4.2 Task.Factory.StartNew()

  • Task.Factory.StartNew()
    • Task.Run()是该方法的简化形式
      • 出现在 .net 4.5
    • 使用场景
      • 指派调度器
      • 使用TaskCreationOptions
        • TaskCreationOptions.LongRunning
          • 告知调度器任务耗时较长,使其能恰当的对该任务进行管理,尽量不霸占整个处理器,且时间延迟对其影响不大
      • 处于性能的考虑需要传递对象的状态

18.4.3 对任务进行资源清理

  • Task实现了IDisposable接口
    • 可以不调用Dispose()方法,性能差别不大
    • 任务在完成前会自动分配一个WaitHandle
    • 程序退出时会调用WaitHandle的终结器进行资源清理。

18.5 基于任务的异步模式

  • 一个完整的流程是一个大的耗时操作,可以细分为多个耗时操作与不耗时操作夹杂在一起。
    • 一个小的耗时操作可以使用同步阻塞方式实现,也可以使用异步不阻塞方式实现
      • 若不需要等待耗时操作的结果,优先使用异步方式实现
  • 若完整流程需要每个细分的耗时操作顺序执行,可以直接在任务中用同步方式实现,也可通过控制流即任务延续的方式调用异步方法

18.5.1 以同步方式调用高延迟操作

  • 阻塞调用线程
public static void Main()
{
	string url = "http://www.IntelliTect.com";
	try
	{
		WebRequest webRequest = WebRequest.Create(url);
		WebResponse response = webRequest.GetResponse();
		using(StreamReader reader = 
			new StreamReader(response.GetResponseStream()))
		{
			string text = reader.ReadToEnd();
			Console.WriteLine(text.Length);
		}
	}
	catch(WebException)
	{
		...
	}
}

18.5.2 TPL异步调用高延迟操作

public static void Main()
{
	string url = "http://www.IntelliTect.com";
	Task task = WWriteWebRequestSizeAsync(url);
	try
	{
		while(!task.Wait(100))
		{
			Console.Write(".");
		}
	}
	catch(AggregateExcepiton excepiton)
	{
		exception = excepiton.Flatten();
		try
		{
			excepiton.Handle(innerException => {
				ExceptionDispatchInfo.Capture(exception.InnerException)
					.Throw();
				return true;
			});
		}
		catch(WebException)
		{
			...
		}
	}
}

private static Task WriteWebRequestSizeAsync(string url)
{
	StreamReader reader = null;
	WebRequest webRequest = WebRequest.Create(url);
	Task task = webRequest.GetResponseAsync()
		.ContinueWith( antecedent => 
		{
			WebResponse response = antecedent.Result;
			reader = new StreamReader(response.GetResponseStream());
			return reader.ReadToEndAsync();
		})
		.Unwrap()
		.ContinueWith( antecedent => 
		{
			if(reader != null) reader.Disponse();
			string text = antecedent.Result;
			Console.WriteLine(text.Length);
		})
		
}
18.5.2.1 异常处理
  • 方式一:利用任务延续
    • 任务控制流变得凌乱
  • 方式二:在委托主体中使用try/catch
    • 每个委托主体都需要自己的一套try/catch逻辑,不利于代码重用
    • task.Wait()仍然还是有可能会引发异常
  • 方式三:只围绕task.Wait()使用 try/catch
    • 任务中的未处理异常在主线程中统一处理
    • AggregateException的InnerException仍可能是AggregateException
      • 由于在后续任务中会调用上一任务的Result属性,而上一任务中可能会出现未处理异常,则此时调用Result会引发AggregateException异常,而任务的异常都统一在主线程中处理,所以还会将该异常再包装放入另一个AggregateException的InnerException中
    • 主线程中处理异常时可使用AggregateException.Flatten()
      • 确保所有异常都跑到第一级
18.5.2.2 TPL实现异步模式的特点
  • 使用任务延续的方式组织代码
    • 相对于同步模式,代码实现复杂
  • 异步方法的返回结果若为Task类型,则Task.ContinueWith()会得到一个Task<Task<T>>类型的对象
  • 在下一个任务开始之前,可以使用Task.Unwrap() 方法除去外层Task的封装

18.5.3 TAP ( Task-based Asynchronous Pattern )

  • 基于任务的异步模式,是对TPL代码的简化
    • 编译器负责将方法转换成任务及一系列任务延续
public static void Main()
{
	string url = "http://www.IntelliTect.com";
	Task task = WWriteWebRequestSizeAsync(url);
	while(!task.Wait(100))
	{
		Console.Write(".");
	}
}

private static Task WriteWebRequestSizeAsync(string url)
{
	try
	{
		WebRequest webRequest = WebRequest.Create(url);
		WebResponse response = await webRequest.GetResponseAsync();
		using(StreamReader reader = 
			new StreamReader(response.GetResponseStream()))
		{
			string text = await reader.ReadToEndAsync();
			Console.WriteLine(text.Length);
		}
	}
	catch(WebException)
	{
		...
	}
}
  • async
    • 修饰封装的TPL功能的代码,告知编译器将代码重写为TPL代码
    • 修饰的方法必须返回Task、Task<T>或void
      • 虽然方法返回类型为Task,但方法中不需要用return返回task对象
        • 编译器将代码重写为TPL,TPL代码返回Task,可用于对任务进行控制,如果不需要控制,可以直接色回值async方法的返回值为void
    • 封装的代码不一定都在异步任务的工作线程中处理,第一个await关键字之前的代码在调用封装代码的线程中处理
    • 封装的代码形式和同步方式一致
  • await
    • 修饰 类型为Task或Task<T>的异步方法表达式
    • await修饰表达式之后的下一行代码是作为await调用的异步方法返回的任务的延续而执行,但延续任务在哪个线程中执行由同步上下文决定,可能是工作者线程,也可能是调用者线程
    • await关键字会对表达式进行求值
    • 异步方法表达式虽然为Task类型,但形式上可以用结果类型的变量进行接收
      • 作为延续任务的输入参数
    • 多个await作为任务的多个延续

18.5.4 异步Lambda

18.5.4.1 异步Lambda
public static void Main()
{
	string url = "http://www.IntelliTect.com";
	Func<string,Task> WriteWebRequestSizeAsync = 
		async (string url) =>
		{
			try
			{
				WebRequest webRequest = WebRequest.Create(url);
				WebResponse response = await webRequest.GetResponseAsync();
				using(StreamReader reader = 
					new StreamReader(response.GetResponseStream()))
				{
					string text = await reader.ReadToEndAsync();
					Console.WriteLine(text.Length);
				}
			}
			catch(WebException)
			{
				...
			}
		}
	Task task = WWriteWebRequestSizeAsync(url);
	while(!task.Wait(100))
	{
		Console.Write(".");
	}
}
  • 将async修饰的封装代码用Lambda表达式进行改写
  • Lambda表达式需要转换为委托类型
18.5.4.2 自定义异步方法
  • 一个异步方法可能是通过多个异步方法组合实现的
  • async和await的作用是组合多个异步任务形成一个异步方法
  • 异步任务
    • 来源一:可以是直接用Task.Run()生成的
      • 告知任务调度器任务需要执行哪些操作,但如何执行由任务调度器控制
    • 来源二:也可以是异步方法返回的
    • 来源三:还可以是用一个Task去关联一个异步操作
      • 该异步操作已经将执行哪些操作以及如何执行都封装在一起,有自己的线程集合以及调度机制,唯一要做的就是启动这个异步操作并接收处理结果而不是再将它再包裹并放入一个任务的工作线程。
      • TaskCompletionSource<T>
        • Task属性
          • 该任务作为异步操作的发起者和最终结果的接受者以及结果暴露的发布者
        • SetResult()
          • 当该方法触发后,Task.Result才能接收到数据,结束阻塞
public static Task<Process> RunProcessAsync(string fileName
		,string arguments = null
		, CancellationToken cancellationToken = default(CancellationToken))
{
	TaskCompletionSource<Process> taskCS = new TaskCompletionSource<Process>();
	Process process = new Process()
	{
		StartInfo = new ProcessStartInfo(fileName)
		{
			UseShellExecute = false,
			Arguments = arguments
		},
		EnableRaisingEvents = true
	};
	process.Exited += (sendar, localEventArgs) =>
	{
		taskCS.SetResult(process);
	};
	cancellationToken.ThrowIfCancellationRequested();
	process.Start();
	cancellationToken.Register(()=>
	{
		process.CloseMainWindow();
	});
	return taskCS.Task;
}
18.5.4.3 等待非Task<T>值
  • await修饰的类型不一定要求是Task或者Task<T>
    • 只要该类型包含GetAwaiter()方法,该方法返回一个对象,对象具有特定的属性和方法能支持编译器进行重写,即Duck Typing
    • 所以在不是基于任务的异步模式中也可以使用await,但async的返回类型必须是void或者是Task以及Task<T>

18.5.5 任务调度器和同步上下文

  • 任务调度器
    • System.Threading.Tasks.TaskScheduler,默认用线程池调度任务。
  • 同步上下文
    • SynchronizationContext
    • 会根据应用程序类型自动设置
      • 控制台应用程序
        • 默认同步上下文为null
      • Asp.net
        • 同步上下文为AspNetSynchronizationContext

18.5.6 async/await和Windows UI

  • Windows UI
    • 同步上下文为DispatcherSynchronizationContext
    • 当需要进行耗时操作时,可用async/await生成异步方法
      • await调用耗时操作,返回一个task
      • await下一行的代码(两个await之间的代码)作为延续任务,延续任务执行前会查找同步上下文,并在同步上下文的线程中执行,即UI线程
  • WPF中的同步高延迟调用
private void PingButton_Click(object sender, RoutedEventArgs e)
{
	//发送到消息泵,但UI线程没空处理
	StatusLabel.Content = "Pinging...";
	UpdateLayout();
	Ping ping = new Ping();
	PingReply pingReply = ping.Send("www.IntelliTect.com");
	//UI线程有空处理时,第二次消息又加入到队列
	//所以Label只会看到应答状态
	StatusLabel.Text = pingReply.Status.ToString();
}
  • WPF中使用await的同步高延迟调用
    • 同步
      • 会等待结果返回再进行下一步操作
      • 而Text的执行确实会等待(但不会阻塞UI线程),且因为同步上下文的关系,都在同一个UI线程中处理
      • 与同步的效果一致,但不阻塞,所以称为同步
private async void PingButton_Click(object sender, RoutedEventArgs e)
{
	StatusLabel.Content = "Pinging...";
	UpdateLayout();
	Ping ping = new Ping();
	//发起异步操作,返回task
	PingReply pingReply = await ping.Send("www.IntelliTect.com");
	//作为task的延续任务
	StatusLabel.Text = pingReply.Status.ToString();
	//在task完成之前,UI线程控制点跳过上面两行代码
	//UI线程有时间可以处理消息泵中的请求,即显示“Pinging...”
}

18.5.7 TAP的目的

  • 需要在不阻塞UI线程的前提下,允许进行长时间运行的活动
  • 支持CPU密集型和非CPU密集型的异步调用
    • CPU密集型
      • 需要CPU进行大量运算
    • 非CPU密集型
      • CPU基本不用工作
      • 例如,需要向服务器请求数据,而服务器执行耗时操作并将结果返回客户端
      • 等待数据返回时,不应该阻塞调用者线程
      • 开辟工作者线程或者Task只为等待返回结果,而不进行任何实际操作,代价又太大
      • 利用TAP中的同步上下文,await获取任务句柄,处理完后结果发送到调用者线程,延续任务在调用者线程执行

18.6 并行迭代

  • 使用场景
    • 一个大型任务可以分解成相互独立、处理器受限的小任务

18.6.1 执行并行

  • Parallel
    • 由TPL提供
    • 方法
      • For( from, to, ( i )=>{} )
      • Foreach( collection, ( item )=>{} )
    • 只有在所有并行都完成后才能继续执行下一步操作
      • 相当于创建任务并立即请求Result

18.6.2 TPL性能调整

  • 采用试探的方式解决过度调度和调度不足的问题
  • 试探方法
    • 方法一:爬山
      • 创建新线程运行任务,并监视和记录性能峰值点
      • 峰值点即为最佳线程数量
    • 方式二:工作窃取
      • 任务少的线程从任务多的线程上窃取部分尚未执行的任务

18.6.3 取消并行循环

  • 并行循环只有在所有迭代都完成之后才会执行后续代码
    • 要在启动并行循环之后再取消它,则调用取消请求的线程不能是执行并行循环的线程
    • 所以,需要新创建一个任务,在该任务中执行并行循环
  • 取消方式
    • public static ParallelLoopResult For(int fromInclusive, int toExclusive, ParallelOptions parallelOptions, Action<int, ParallelLoopState> body)
      • ParallelOptions
        • CancellationToken
          • 可在其他线程上控制循环取消
        • int MaxDegreeOfParallelism
          • 最大并行数
        • TaskScheduler
          • 指定任务调度器
  • 判断循环是否被取消
    • OperationCanceledException
      • 循环取消会抛出
public static void Main()
{
	CancellationTokenSource cts = new CancellationTokenSource();
	ParallelOptions parrallelOptions = new ParrallelOptions
	{
		CancellationToken = cts.Token
	};
	cts.Token.Register(()=> Console.WriteLine("Cancelling..."));
	
	Task task = Task.Run(()=>
	{
		try
		{
			Parallel.ForEach(
				files, parallelOptions,
				(fileName, loopState)=>
					{
						Encrypt(fileName);
					});
		}
		catch(OperationCanceledException){}
	});

	Console.Read();
	cts.Cancel();
	task.Wait();
}

18.6.4 中断并行循环

  • 在并行循环内部中断循环,类似for循环内部调用break
  • 中断方式
    • public static ParallelLoopResult For(int fromInclusive, int toExclusive, ParallelOptions parallelOptions, Action<int, ParallelLoopState> body)
      • ParallelLoopState
        • 可在循环体内部中止循环
        • Break()
          • 表示大于当前索引值的循环不开始
        • Stop()
          • 表示未开始的循环不开始
      • ParallelLoopResult
               循环中的有关信息
        • IsCompleted
          • 是否所有迭代都已开始
        • LowestBreakIteration
          • 执行了一个中断的、索引最低的迭代
          • 类型long?

18.7 并行执行LINQ查询

18.7.1 PLINQ

  • AsParallel()
    • 由System.Linq.ParallelEnumerable类提供的拓展方法,作用在IEnumerable上,返回ParallelQuery类型,该类型派生自IEnumerable
    • 是在调用者线程上执行的同步操作
    • 语法糖

18.7.2 取消PLINQ查询

  • 类似并行迭代的取消模式
  • 取消方式
    • ParallelQuery<T>类型包含WithCancellation() 拓展方法,可执行取消
      • 取消查询会抛出OperationCanceledException异常
      • 如果将CancellationToken作为Task.Run()的第二个参数传递,则会将取消查询的异常包裹在AggregateException中,InnerException中的异常类型为TaskCanceledException
public static void Main()
{
	CancellationTokenSource cts = new CancellationTokenSource();
	
	Task task = Task.Run(()=>
	{
		data = ParallelEncrypt(data, cts.Token);
	}, cts.Token);

	Console.Read();
	if(!task.IsCompleted)
	{
		cts.Cancel();
		try
		{
			task.Wait();
		}
		catch(AggregateException exception)
		{
			...
		}
	}
}

public static List<string> ParallelEncrypt
	(List<string> data, CancellationToken cancellationToken)
{
	return data.AsParallel().WithCancellation(cancellationToken)
			.select((item)=>Encrypt(item)).ToList();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值