C # MultiThread Safe Class Design - PullRequest
       12

C # MultiThread Safe Class Design

3 голосов
/ 13 мая 2010

Я пытаюсь создать класс, и у меня возникают проблемы с доступом к некоторым вложенным полям, и у меня есть некоторые вопросы по поводу того, насколько многопоточен безопасный проект в целом. Я хотел бы знать, если у кого-то есть лучшее представление о том, как это должно быть разработано или какие-либо изменения, которые должны быть сделаны?

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 Ответ

2 голосов
/ 13 мая 2010

Сделать класс потокобезопасным - это чертовски сложно сделать.

Первый, наивный способ, который многие пытаются использовать, - это просто добавить блокировку и убедиться, что ни один код, касающийся изменяемых данных, не сделает этого без использования блокировки. Под этим я подразумеваю, что все в классе, подлежащем изменению, должно сначала заблокировать блокирующий объект, прежде чем касаться данных, будь то чтение или запись в него.

Однако, если это ваше решение, то вам, вероятно, вообще ничего не нужно делать с кодом, просто документально подтвердите, что класс не является поточно-ориентированным, и предоставьте его программисту, который его использует.

Почему?

Потому что вы фактически только что сериализовали весь доступ к нему. Два потока, которые пытаются использовать класс одновременно, даже если они касаются отдельных его частей, будут блокироваться. Один из потоков получит доступ, другой будет ожидать завершения первого.

Это на самом деле препятствует многопоточному использованию вашего класса, поэтому в этом случае вы добавляете накладные расходы на блокировку для вашего класса, а не получаете от этого никаких преимуществ. Да, ваш класс теперь «потокобезопасен», но на самом деле он не очень хороший гражданин.

Другой способ - начать добавлять гранулярные блокировки или писать конструкции без блокировок (очень сложно), чтобы, если две части объекта не всегда были связаны, код, обращающийся к каждой части, имел свою собственную блокировку. Это позволило бы нескольким потокам, которые обращаются к различным частям данных, работать параллельно, не блокируя друг друга.

Это становится трудным везде, где вам нужно работать с более чем одной частью данных за раз, так как вам нужно быть очень осторожным, чтобы взять блокировки в правильном порядке или избежать взаимоблокировок. Ваш класс должен нести ответственность за обеспечение того, чтобы блокировки были приняты в правильном порядке, а не код, который использует класс.

Что касается вашего конкретного примера, мне кажется, что части, которые будут меняться от фоновых потоков, являются только логическими значениями "подключено к устройству". В этом случае я бы сделал это поле изменчивым и использовал бы блокировку вокруг каждого. Однако, если список устройств изменится с фоновых потоков, вы быстро столкнетесь с проблемами.

Сначала вы должны попытаться определить все части, которые будут изменены фоновыми потоками, а затем разработать сценарии того, как вы хотите, чтобы изменения распространялись на другие потоки, как реагировать на изменения и т. Д.

...