• 编写高质量代码改善C#程序的157个建议——建议22:确保集合的线程安全


    建议22:确保集合的线程安全

    集合线程安全是指多个线程上添加或删除元素时,线程键必须保持同步。

    下面代码模拟了一个线程在迭代过程中,另一个线程对元素进行了删除。

        class Program
        {
            static List<Person> list = new List<Person>()
                {
                    new Person() { Name = "Rose", Age = 19 },
                    new Person() { Name = "Steve", Age = 45 },
                    new Person() { Name = "Jessica", Age = 20 },
                };
            static AutoResetEvent autoSet = new AutoResetEvent(false);
    
            static void Main(string[] args)
            {
                Thread t1 = new Thread(() =>
                {
                    //确保等待t2开始之后才运行下面的代码
                    autoSet.WaitOne();
                    foreach (var item in list)
                    {
                        Console.WriteLine("t1:" + item.Name);
                        Thread.Sleep(1000);
                    }
                });
                t1.Start();
                Thread t2 = new Thread(() =>
                {
                    //通知t1可以执行代码
                    autoSet.Set();
                    //沉睡1秒是为了确保删除操作在t1的迭代过程中
                    Thread.Sleep(1000);
                    list.RemoveAt(2);
                });
                t2.Start();
            }
        }
    
        class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }
        }

    以上代码运行过程会抛出InvalidOperationException:“集合已修改,可能无法执行枚举。”

    早在泛型集合出现之前,非泛型集合一般提供一个SyncRoot属性,要保证非泛型集合的线程安全,可以通过锁定该属性来实现。如果上面的集合用ArrayList代替,保证其线程安全则应该在迭代和删除的时候都加上lock,代码如下:

            static ArrayList list = new ArrayList()
            {
                        new Person() { Name = "Rose", Age = 19 },
                        new Person() { Name = "Steve", Age = 45 },
                        new Person() { Name = "Jessica", Age = 20 },
            };
            static AutoResetEvent autoSet = new AutoResetEvent(false);
    
            static void Main(string[] args)
            {
                Thread t1 = new Thread(() =>
                {
                    //确保等待t2开始之后才运行下面的代码
                    autoSet.WaitOne();
                    lock (list.SyncRoot)
                    {
                        foreach (Person item in list)
                        {
                            Console.WriteLine("t1:" + item.Name);
                            Thread.Sleep(1000);
                        }
                    }
                });
                t1.Start();
                Thread t2 = new Thread(() =>
                {
                    //通知t1可以执行代码
                    autoSet.Set();
                    //沉睡1秒是为了确保删除操作在t1的迭代过程中
                    Thread.Sleep(1000);
                    lock (list.SyncRoot)
                    {
                        list.RemoveAt(2);
                        Console.WriteLine("删除成功");
                    }
                });
                t2.Start();
            }
    

    以上代码不会抛出异常,因为锁定通过互斥的机制保证了同一时刻只能有一个线程操作集合元素。我们进而发现泛型集合没有这样的属性,必须要自己创建一个锁定对象来完成同步任务。可以通过new一个静态对象来进行锁定,代码如下:

            static List<Person> list = new List<Person>()
                {
                    new Person() { Name = "Rose", Age = 19 },
                    new Person() { Name = "Steve", Age = 45 },
                    new Person() { Name = "Jessica", Age = 20 },
                };
            static AutoResetEvent autoSet = new AutoResetEvent(false);
            static object sycObj = new object();
    
            static void Main(string[] args)
            {
                //object sycObj = new object();
                Thread t1 = new Thread(() =>
                {
                    //确保等待t2开始之后才运行下面的代码
                    autoSet.WaitOne();
                    lock (sycObj)
                    {
                        foreach (Person item in list)
                        {
                            Console.WriteLine("t1:" + item.Name);
                            Thread.Sleep(1000);
                        }
                    }
                });
                t1.Start();
                Thread t2 = new Thread(() =>
                {
                    //通知t1可以执行代码
                    autoSet.Set();
                    //沉睡1秒是为了确保删除操作在t1的迭代过程中
                    Thread.Sleep(1000);
                    lock (sycObj)
                    {
                        list.RemoveAt(2);
                        Console.WriteLine("删除成功");
                    }
                });
                t2.Start();
            }

    转自:《编写高质量代码改善C#程序的157个建议》陆敏技

  • 相关阅读:
    Visual C++6.0 调用Visual Basic 6.0写的Microsoft Communications Control(ActiveX)的使用疑难及解决办法
    Associating Icons with a Category 与 恶作剧软件 有关系吗?
    WPF/Silverlight Button Styles and Templates
    Notepad++ 备忘录一
    冥思苦想,木疙瘩也能崩出个豆:扯一下各大软件的用户体验
    生活小窍门。
    Bug验证:.Net 4 下,貌似发现一个bug。如果是真,.Net组的员工该打屁股。
    两台硬件和软件配置完全相同的机器A和B,现在要用系统自带的Copy功能把A上的一个文件,复制到B上。在哪台机器上执行程序,效率更高?
    WPF 遍历 DataGrid 每行的控件
    IE ActiveX Control 和RIA
  • 原文地址:https://www.cnblogs.com/jesselzj/p/4730830.html
Copyright © 2020-2023  润新知