本系列将和大家分享.Net中的异步和多线程,本文主要介绍的是多线程异常处理、线程取消、多线程的临时变量问题、线程安全和锁lock等。

本文是.NET异步和多线程系列第四章,主要介绍的是多线程异常处理、线程取消、多线程的临时变量问题、线程安全和锁lock等。

一、多线程异常处理

多线程里面抛出的异常,会终结当前线程,但是不会影响别的线程。那线程异常哪里去了? 被吞了。

假如想获取异常信息,这时候要怎么办呢?下面来看下其中的一种写法(不推荐):

/// <summary>/// 1 多线程异常处理和线程取消/// 2 多线程的临时变量/// 3 线程安全和锁lock/// </summary>private void btnThreadCore_Click(object sender, EventArgs e){ Console.WriteLine($"****************btnThreadCore_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +  $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); #region 多线程异常处理 {  try  {   List<Task> taskList = new List<Task>();   for (int i = 0; i < 100; i++)   {    string name = $"btnThreadCore_Click_{i}";    taskList.Add(Task.Run(() =>    {     if (name.Equals("btnThreadCore_Click_11"))     {      throw new Exception("btnThreadCore_Click_11异常");     }     else if (name.Equals("btnThreadCore_Click_12"))     {      throw new Exception("btnThreadCore_Click_12异常");     }     else if (name.Equals("btnThreadCore_Click_38"))     {      throw new Exception("btnThreadCore_Click_38异常");     }     Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");    }));   }   //多线程里面抛出的异常,会终结当前线程,但是不会影响别的线程。   //那线程异常哪里去了? 被吞了。   //假如我想获取异常信息,还需要通知别的线程   Task.WaitAll(taskList.ToArray()); //1 可以捕获到线程的异常  }  catch (AggregateException aex) //2 需要try-catch-AggregateException  {   foreach (var exception in aex.InnerExceptions)   {    Console.WriteLine(exception.Message);   }  }  catch (Exception ex) //可以多catch 先具体再全部  {   Console.WriteLine(ex);  }  //线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消?  //工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作。 } #endregion 多线程异常处理 Console.WriteLine($"****************btnThreadCore_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +  $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");}

上面的这种写法往往太极端了,一下子捕获了所有的异常。在真实工作中,线程异常后通常是需要通知别的线程(进行线程取消),而不是等到WaitAll。

工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作。具体的我们往下继续看。

二、线程取消

多线程并发任务,某个失败后,希望通知别的线程都停下来,要如何实现呢?

Thread.Abort--终止线程;向当前线程抛一个异常然后终结任务;线程属于OS资源,可能不会立即停下来。非常不建议这样子去做,该方法现在也被微软给废弃了。

既然Task不能外部终止任务,那只能自己终止自己(上帝才能打败自己),下面我们来看下具体的代码:(推荐)

#region 线程取消{ //多线程并发任务,某个失败后,希望通知别的线程都停下来,要如何实现呢? //Thread.Abort--终止线程;向当前线程抛一个异常然后终结任务;线程属于OS资源,可能不会立即停下来。非常不建议这样子去做,该方法现在也被微软给废弃了。 //Task不能外部终止任务,只能自己终止自己(上帝才能打败自己) //cts有个bool属性IsCancellationRequested 初始化是false //调用Cancel方法后变成true(不能再变回去),可以重复Cancel try {  CancellationTokenSource cts = new CancellationTokenSource();  List<Task> taskList = new List<Task>();  for (int i = 0; i < 50; i++)  {   string name = $"btnThreadCore_Click_{i}";   taskList.Add(Task.Run(() =>   {    try    {     if (!cts.IsCancellationRequested)      Console.WriteLine($"This is {name} 开始 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");     Thread.Sleep(new Random().Next(50, 100));     if (name.Equals("btnThreadCore_Click_11"))     {      throw new Exception("btnThreadCore_Click_11异常");     }     else if (name.Equals("btnThreadCore_Click_12"))     {      throw new Exception("btnThreadCore_Click_12异常");     }     else if (name.Equals("btnThreadCore_Click_13"))     {      cts.Cancel();     }     if (!cts.IsCancellationRequested)     {      Console.WriteLine($"This is {name}成功结束 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");     }     else     {      Console.WriteLine($"This is {name}中途停止 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");      return;     }     }    catch (Exception ex)    {     Console.WriteLine(ex.Message);     cts.Cancel();    }   }, cts.Token));   //加参数cts.Token目的是:在Cancel时还没有启动的任务,就不启动了。   //但是所有没有启动的任务都会抛出一个异常cts.Token.ThrowIfCancellationRequested  }  //1 准备cts 2 try-catch-cancel 3 Action要随时判断IsCancellationRequested  //尽快停止,肯定有延迟,在判断环节才会结束  Task.WaitAll(taskList.ToArray());  //如果线程还没启动,能不能就别启动了?加参数cts.Token  //1 启动线程传递Token 2 异常抓取   //在Cancel时还没有启动的任务,就不启动了;也是抛异常,cts.Token.ThrowIfCancellationRequested } catch (AggregateException aex) {  foreach (var exception in aex.InnerExceptions)  {   Console.WriteLine(exception.Message);  } } catch (Exception ex) {  Console.WriteLine(ex.Message); }}#endregion 线程取消

CancellationTokenSource有个bool属性IsCancellationRequested,初始化是false,调用Cancel方法后变成true(不能再变回去),可以重复Cancel。

值得一提的是,使用Task.Run启动线程的时候还传了一个cts.Token的参数,目的是:在Cancel时还没有启动的任务,就不启动了,但是所有没有启动的任务都会抛出一个异常cts.Token.ThrowIfCancellationRequested。

注意:此处的线程停止也只能说是尽快停止,肯定有延迟,在判断环节才会结束。

三、多线程的临时变量问题

#region 多线程的临时变量问题{ //多线程的临时变量问题,线程是非阻塞的,延迟启动的;线程执行的时候,i已经是5了。 for (int i = 0; i < 5; i++) {  Task.Run(() =>  {   //此处i都是5   Console.WriteLine($"This is btnThreadCore_Click_{i} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");  }); } //k是闭包里面的变量,每次循环都有一个独立的k //5个k变量 1个i变量 for (int i = 0; i < 5; i++) {  int k = i;  Task.Run(() =>  {   Console.WriteLine($"This is btnThreadCore_Click_{i}_{k} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");  }); }}#endregion 多线程的临时变量问题

运行结果如下:

.NET异步和多线程系列(四)_多线程

四、线程安全和锁lock

线程安全:如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的。

线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要是多线程都能访问和修改的就有可能是非线程安全。

非线程安全是因为多个线程相同操作,出现了覆盖,那要怎么解决?

方案1:使.........