欢迎光临散文网 会员登陆 & 注册

.Net多线程编程(误用点分析)

2023-06-10 15:31 作者:杨程杰Yc86  | 我要投稿

建个控制台(.net framework)

 class Program

  {

    #region 1 共享变量问题

    /// <summary>

    /// 错误写法:

    /// 所有的任务可能会共享同一个变量,所以输出结果可能会一样。

    /// </summary>

    public static void Error()

    {

      for (int i = 0; i < 10; i++)

      {

        Task.Run(() => { Console.WriteLine("{0}", i); });

      }

    }

    /// <summary>

    /// 正确写法:

    /// 将变量i赋给局部变量temp,使得每一个任务使用不同的i值。

    /// </summary>

    public static void Right()

    {

      for (int i = 0; i < 10; i++)

      {

        int temp = i;

        Task.Run(() => { Console.WriteLine("{0}", temp); });

      }

    }

    #endregion


    #region 2 不要清理挂起任务所需资源

    /// <summary>

    /// 错误写法:

    /// 异步输出文本内容,所以在未使用完StreamReader的时候,变量sr已经离开它的作用域,调用Dispose方法。

    /// System.ObjectDisposedException:“无法从已关闭的 TextReader 中读取。”

    /// </summary>

    public static void Error2()

    {

      using (StreamReader sr = new StreamReader(@"D:\说明.txt", Encoding.Default))

      {

        Task.Run(() => { Console.WriteLine("输出:{0}", sr.ReadLine()); });

      }

    }

    /// <summary>

    /// 正确写法:

    /// </summary>

    public static void Right2()

    {

      using (StreamReader sr = new StreamReader(@"D:\说明.txt", Encoding.UTF8))

      {

        var task = Task.Run(() => { Console.WriteLine("输出:{0}", sr.ReadLine()); });

        task.Wait();

      }

    }

    #endregion


    #region 3 避免锁定this,typeof(type),string

    //正确的做法:定义一个object类型的私有只读字段,锁定之。

    #endregion


    #region 4 关于WaitHandle.WaitAll的waitHandles的数目必须小于等于64个 

    public static void Error4()

    {

      ManualResetEvent[] manualEvents = new ManualResetEvent[65];

      try

      {

        for (int i = 0; i < 65; i++)

        {

          var temp = i;

          Task.Run(() =>

          {

            manualEvents[temp] = new ManualResetEvent(false);

            Console.WriteLine("{0}", temp);

            manualEvents[temp].Set();

          });

        }

        WaitHandle.WaitAll(manualEvents);

      }

      catch (Exception ae)

      {

        Console.WriteLine(ae.Message);

      }

    }

    #endregion


    #region 6 是否该释放Task资源

    /// <summary>

    /// 建议调用Dispose,但不调用也不是一个严重的错误。

    /// 注意在Task任务处于某些状态时是不允许释放资源的,否则会报错。

    /// </summary>

    public static void CatchException()

    {

      try

      {

        Console.WriteLine("开始");

        var task = Task.Run(() =>

        {

          //throw new Exception("抛异常"); 

        });

        //注掉下面这行代码,观察异常结果

        task.Wait();

        task.Dispose();

        Console.WriteLine("结束");

      }

      catch (Exception ex)

      {

        Console.WriteLine(ex.Message);

      }

    }

    #endregion


    #region 7 死锁演示

    /// <summary>

    /// 假设tsak1和task2都在获得第二个锁(对tsak1来说它请求的第二个锁是LockedObj2 ,而对task2来说则是LockedObj1 )之前成功获得了第一个锁,就会发生死锁。

    /// </summary>

    private static readonly Object LockedObj1 = new object();

    private static readonly Object LockedObj2 = new object();

    public static void LockShow()

    {

      var task1 = Task.Run(() =>

      {

        lock (LockedObj1)

        {

          Console.WriteLine("get LockedObj1");

          lock (LockedObj2)

          {

            Console.WriteLine("get LockedObj2....");

          }

        }

      });

      var task2 = Task.Run(() =>

      {

        lock (LockedObj2)

        {

          Console.WriteLine("get LockedObj2");

          lock (LockedObj1)

          {

            Console.WriteLine("get LockedObj1....");

          }

        }

      });

    }

    #endregion


    #region 8 不要调用Thread.Abort方法。

    /// <summary>

    /// Task没有提供Abort方法,使用新的TPL(.NET 4.0以后),不会想到这个问题,一般使用CancellationToken来控制取消任务。

    /// </summary>

    #endregion


    #region 9 确保共享变量是安全的

    //反复运行,可观察到不一样的结果,下图所示。

    public static void Func()

    {

      string s = "ASDFGH";

      Parallel.Invoke(

       () => { s = s.Replace("A", "1"); s = s.Replace("S", "1s"); },

       () => { s = s.Replace("A", "2"); s = s.Replace("S", "2s"); },

       () => { s = s.Replace("A", "3"); });

      Console.WriteLine(s);

    }

    #endregion


    #region 10 处理器超额申请与申请不足

    public static void Func10()

    {

      ParallelOptions po = new ParallelOptions();

      //超额申请,处理器只有4个逻辑内核,结果设置并行度为10且是个逻辑内核均在工作,等待的任务数量大于0.

      po.MaxDegreeOfParallelism = 10;

      //申请不足,处理器有4个逻辑内核,却指定并行度为3,还有一个空闲的内核没有被占用(也有可能被其他线程占用,这里假设在指定并行度为3的情况下,另一个内核空闲)

      po.MaxDegreeOfParallelism = 3;

      List<int> list = new List<int>();

      Parallel.ForEach(list, po, m =>

      {

        //业务

      });

    }

    #endregion


    static void Main(string[] args)

    {

      Error();

      Right();


      Error2();

      Right2();


      Error4();


      #region 5 无法捕获异常的情形

      try

      {

        var task = Task.Run(() => { throw new Exception("抛异常"); });

        //如果将下面这行代码注掉,则无法抛出异常

        task.Wait();

      }

      catch (Exception ex)

      {

        Console.WriteLine(ex.Message);

      }

      #endregion


      CatchException();

      LockShow();

      Func();

      Func10();

    }

  }

.Net多线程编程(误用点分析)的评论 (共 条)

分享到微博请遵守国家法律