c#线程锁定和监视

时间:2017-10-25 11:11:29

标签: c# multithreading locking monitor

我目前遇到了多线程和访问静态列表的问题。静态列表包含具有多个属性的所有项目。这些项目标有Guid。主工作线程会更改静态列表中任何项的某些属性。子线程都有自己的Guid,这个Guid他们在静态列表中读取自己的项目。在特定事件之后,他们从静态列表中删除已分配的元素。

enter image description here

为了获得源代码,我将代码分解为必要的方法和类。工作线程具有以下简化代码

public void RunWork()
{
    Random random = new Random();
    Int32 index = -1;
    while (!Kill)
    {
        Thread.Sleep(1);
        if (MainWindow.Clients != null)
        {
            index = random.Next(0, MainWindow.Clients.Count);

            MainWindow.Clients[index].State = MainWindow.RandomString(9);
        }
    }
}

每个子线程都有以下简化代码

public void RunChild()
{
    Random random = new Random();
    while (!Kill)
    {
        Thread.Sleep(100);
        if (MainWindow.Clients.Any(x => x.Id == Id))
        {
            this.State = MainWindow.Clients.First(x => x.Id == Id).State;
        }
        Thread.Sleep(random.Next(50));
        if (random.Next(100) % 90 == 0)
        {
            Kill = true;
            MainWindow.Clients.RemoveAll(x => x.Id == Id);
        }
    }
}

如果子项从MainWindow.Clients列表中删除,则工作线程抛出异常,它尝试访问的索引不存在。

我在lock的每次访问时都添加了MainWindow.Clients个参数,但这并不妨碍工作线程访问已删除的项目。我也尝试了Monitor.Enter(MainWindow.Clients)Monitor.Exit(MainWindow.Clients),但结果与lock相同。

静态列表MainWindow.Clients是在任何线程运行之前创建的,永远不会被重新创建或处理。

如果lock方法中的此代码块周围设置了RunWork()语句

lock (MainWindow.Clients)
{
    Thread.Sleep(1);
    if (MainWindow.Clients != null)
    {
        index = random.Next(0, MainWindow.Clients.Count);

        MainWindow.Clients[index].State = MainWindow.RandomString(9);
    }
}

为什么它不阻止子线程更改行之间的列表 设置随机索引并访问列表的位置?

更新1: 以下代码仍会在MainWindow.Clients[index].State = MainWindow.RandomString(9);处抛出IndexOutOfRangeException:

public void RunWork()
{
    Random random = new Random();
    Int32 index = -1;
    while (!Kill)
    {
        Thread.Sleep(1);
        if (MainWindow.Clients != null)
        {
            lock (MainWindow.Clients)
            {
                index = random.Next(0, MainWindow.Clients.Count);

                MainWindow.Clients[index].State = MainWindow.RandomString(9);
            }
        }
    }
}

public void RunChild()
{
    Random random = new Random();
    while (!Kill)
    {
        Thread.Sleep(100);
        if (MainWindow.Clients.Any(x => x.Id == Id))
        {
            this.State = MainWindow.Clients.First(x => x.Id == Id).State;
        }
        Thread.Sleep(random.Next(50));
        if (random.Next(100) % 90 == 0)
        {
            Kill = true;
            lock (MainWindow.Clients)
            {
                MainWindow.Clients.RemoveAll(x => x.Id == Id);
            }
        }
    }
}

更新2: Here是快速示例应用程序的完整代码

更新3:我已编辑了我的代码,并使用锁定语句包含了MainWindow.Clients的所有访问权限。但是线程在锁定时仍然访问变量: enter image description here

1 个答案:

答案 0 :(得分:-1)

我不确定您到底想要实现的目标,但我已经写了一些可能有助于您找到正确解决方案的内容。抱歉缺乏正确性 - 时间紧迫; - )

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace ConcurrentCollectionTest
{
    internal class Client
    {
        public string State
        {
            get; set;
        }
        public string Name
        {
            get;
            internal set;
        }
    }

    internal class MainWindow
    {
        private ConcurrentDictionary<int, Client> _dict = new ConcurrentDictionary<int, Client>();

        public IDictionary<int, Client> Clients
        {
            get
            {
                return _dict;
            }
        }
    }

    internal class Program
    {
        private static bool killAll = false;
        private static MainWindow mainWindow = new MainWindow();
        private static int id = -100;
        private static string state = "Initial";
        private static Random random = new Random();
        private static object lockObject = new object();

        internal static string RandomString(int v)
        {
            int k = random.Next(0, v);
            return k.ToString();
        }

        public static void RunChild()
        {
            Debug.WriteLine($"child running {Thread.CurrentThread.Name}");
            bool killThis = false;
            while (!killThis && !killAll)
            {
                Thread.Sleep(100);
                Client client = null;
                if (mainWindow.Clients.TryGetValue(id, out client))
                {
                    state = client.State;
                }

                Thread.Sleep(random.Next(50));

                if (random.Next(100) % 90 == 0)
                {
                    Debug.WriteLine($"killing {Thread.CurrentThread.Name}");
                    killThis = true;
                   lock (lockObject)
                {
                    mainWindow.Clients.Remove(id);
                }
                }
            }
        }

        public static void RunWork()
        {
            Console.WriteLine("RunWork");
            Random random = new Random();
            Int32 index = -1;
            while (!killAll)
            {
                if (!mainWindow.Clients.Any())
                {
                    killAll = true;
                    break;
                }
                Thread.Sleep(100);
                // edit: still need lock here as count can change in between
            Client client = null;
            lock (lockObject)
            {
                index = random.Next(0, mainWindow.Clients.Count);
                client = mainWindow.Clients[index];
            }
                Debug.WriteLine($"Changing {client.Name}");
                client.State = RandomString(9);                 
            }

            Console.WriteLine("Worker killed");
        }

        private static void Main(string[] args)
        {
            Console.WriteLine("Starting. Enter id or kill");

            for (int i = 0; i < 100; i++)
            {
                mainWindow.Clients.Add(i, new Client
                {
                    Name = $"Client {i:000}",
                    State = "Unknown"
                });
            }

            var worker = new Thread(RunWork);
            worker.Start();

            var threadList = new List<Thread>();
            threadList.Add(worker);

            for (int i = 0; i < 10; i++)
            {
                var thread = new Thread(RunChild)
                {
                    Name = $"Child {i:00}"
                };

                threadList.Add(thread);
                thread.Start();
            }

            while (!killAll)
            {
                var str = Console.ReadLine();
                if (str.Equals("kill", StringComparison.InvariantCultureIgnoreCase))
                {
                    killAll = true;
                    break;
                }

                int enteredId = -1;

                if (int.TryParse(str, out enteredId))
                {
                    id = enteredId;
                }
            }

            foreach (var thread in threadList)
            {
                thread.Join();
            }

            Console.WriteLine("all dead");
        }
    }
}