C#MultiThread安全类设计

时间:2010-05-13 06:21:24

标签: c# design-patterns c#-4.0 class-design

我正在尝试设计一个类,我在访问某些嵌套字段时遇到问题,而且我对整个设计的多线程安全性有一些顾虑。我想知道是否有人更清楚应该如何设计或应该做出哪些改变?

using System;
using System.Collections;

namespace SystemClass
{
public class Program
{
    static void Main(string[] args)
    {
        System system = new System();

        //Seems like an awkward way to access all the members
        dynamic deviceInstance = (((DeviceType)((DeviceGroup)system.deviceGroups[0]).deviceTypes[0]).deviceInstances[0]);
        Boolean checkLocked = deviceInstance.locked;

        //Seems like this method for accessing fields might have problems with multithreading
        foreach (DeviceGroup dg in system.deviceGroups)
        {
            foreach (DeviceType dt in dg.deviceTypes)
            {
                foreach (dynamic di in dt.deviceInstances)
                {
                    checkLocked = di.locked;
                }
            }
        }
    }
}

public class System
{
    public ArrayList deviceGroups = new ArrayList();

    public System()
    {   
        //API called to get names of all the DeviceGroups
        deviceGroups.Add(new DeviceGroup("Motherboard"));
    }
}

public class DeviceGroup
{
    public ArrayList deviceTypes = new ArrayList();

    public DeviceGroup() {}

    public DeviceGroup(string deviceGroupName)
    {
        //API called to get names of all the Devicetypes
        deviceTypes.Add(new DeviceType("Keyboard"));
        deviceTypes.Add(new DeviceType("Mouse"));
    }
}

public class DeviceType
{
    public ArrayList deviceInstances = new ArrayList();
    public bool deviceConnected;

    public DeviceType() {}

    public DeviceType(string DeviceType)
    {
        //API called to get hardwareIDs of all the device instances
        deviceInstances.Add(new Mouse("0001"));
        deviceInstances.Add(new Keyboard("0003"));
        deviceInstances.Add(new Keyboard("0004"));

        //Start thread CheckConnection that updates deviceConnected periodically
    }

    public void CheckConnection()
    {
        //API call to check connection and returns true
        this.deviceConnected = true;
    }
}

public class Keyboard
{
    public string hardwareAddress;
    public bool keypress;
    public bool deviceConnected;

    public Keyboard() {}

    public Keyboard(string hardwareAddress)
    {
        this.hardwareAddress = hardwareAddress;
        //Start thread to update deviceConnected periodically
    }

    public void CheckKeyPress()
    {
        //if API returns true
        this.keypress = true;
    }
}

public class Mouse
{
    public string hardwareAddress;
    public bool click;

    public Mouse() {}

    public Mouse(string hardwareAddress)
    {
        this.hardwareAddress = hardwareAddress;
    }

    public void CheckClick()
    {
        //if API returns true
        this.click = true;
    }
}

}

1 个答案:

答案 0 :(得分:2)

使类线程安全是一件困难的事情。

许多人倾向于尝试的第一种天真的方式是添加一个锁并确保没有使用锁的情况下没有触及可变数据的代码。我的意思是,类中所有可能发生变化的东西,必须首先锁定锁定对象,然后再触摸数据,只需从中读取或写入数据。

但是,如果这是您的解决方案,那么您可能根本不应对代码执行任何操作,只记录该类不是线程安全的,并将其留给使用它的程序员。

为什么?

因为您已经有效地序列化了对它的所有访问权限。试图同时使用该类的两个线程,即使它们触及它的单独部分,也会阻塞。其中一个线程将被授予访问权限,另一个将等待第一个线程完成。

这实际上是在阻止您的类的多线程使用,所以在这种情况下,您将增加锁定到您的类的开销,而实际上并没有从中获得任何好处。是的,你的班级现在是“线程安全的”,但它实际上并不是一个好的线程公民。

另一种方法是开始添加粒度锁,或者编写无锁结构(非常难),这样如果对象的两个部分并不总是相关,那么访问每个部分的代码都有自己的锁。这将允许访问数据的不同部分的多个线程并行运行而不会相互阻塞。

当您需要一次处理多个数据部分时,这变得很难,因为您需要非常小心地按正确顺序获取锁定,否则会遇到死锁。确保以正确的顺序执行锁定应该是您的类的责任,而不是使用该类的代码。

至于你的具体例子,它看起来好像从后台线程改变的部分只是“是设备连接”的布尔值。在这种情况下,我会使该字段变化,并使用每个锁定。但是,如果设备列表将从后台线程更改,那么您将很快遇到问题。

您应首先尝试识别将由后台线程更改的所有部分,然后设计场景,以了解您希望如何将更改传播到其他线程,如何对更改做出反应等。