Правильный способ обработки блокировки классов, совместно используемых в потоках

#c# #multithreading

#c# #многопоточность

Вопрос:

Я очистил и сократил фрагмент моего кода для многопользовательской игры, чтобы показать, чего я хотел бы достичь. Итак, вот оно:

     public class Subject {
        public List<IObject> Objects = new List<IObject>();
    }

    public interface IOpenable
    {
        void Open(Subject by, params string[] p);
        void Close(Subject by, params string[] p);
        bool IsOpen { get; }
    }

    public interface IObject {
        void Pickup(Subject by, params string[] p);
        void Drop(Subject by, params string[] p);
    }

    public class Box : IObject, IOpenable
    {
        bool opened = false;
        Subject owner = null;

        public void Pickup(Subject subject, params string[] p) {
            subject.Objects.Add(this);
            this.owner = subject;
        }

        public void Drop(Subject subject, params string[] p)
        {
            subject.Objects.Remove(this);
            this.owner = null;
        }

        public void Open(Subject by, params string[] p)
        {
            this.opened = true;
        }

        public void Close(Subject by, params string[] p)
        {
            this.opened = false;
        }

        public bool IsOpen { get { return opened; } }
    }
  

Что я хотел бы знать, так это:
Как запретить какому-либо пользователю (выполняющему код из другого потока) открывать окно, которое в данный момент получает какой-либо другой пользователь.
Я думал о нескольких способах, но я думаю, что люди здесь часто выдвигают умные идеи, которые могли бы помочь мне избежать какой-нибудь глупой проблемы с дизайном.

РЕДАКТИРОВАТЬ: Как предложено в ответах, использовать ключевое слово lock в методе open: это не совсем то, что я хочу, я попытаюсь объяснить, что разрешено, а что нет:

Сетевые запросы, которые мы получаем в качестве входных данных, каким-то образом асинхронны и выходят из строя, если выполняются быстро.

  • (1) Пользователь 1 выдает ОКНО ВВОДА команды
  • (2) Пользователь 1 выдает команду ОТКРЫТЬ ОКНО
  • (3) Пользователь 1 выдает команду ЗАКРЫТЬ ОКНО
  • (4) Пользователь 2 выдает команду ОТКРЫТЬ ОКНО
  • (5) Пользователь 2 выдает ОКНО ПОИСКА команд
  • (6) Пользователь 1 выдает команду ОТКРЫТЬ ОКНО

Мы получаем такой порядок:

2,3,1,5,4,6

 2 - allow
3 - allow
1 - allow [remains in execution and has not set the owner]
5(comes in between 1) - allow
4(comes in between 1) - disallow (not because already open but because 1 is in execution)
6(comes in between 1) - allow since it is from user 1, and he is currently picking it up
  

Спасибо!

Комментарии:

1. Решение, которое я опубликовал, удовлетворит вашим новым условиям — TryOpen() вернет false при выполнении (3). Вы можете применить тот же подход к другим методам, удовлетворяющим дополнительным условиям, если требуется.

2. насколько я знаю, в tcp последовательность сообщений гарантируется, поэтому худшее, что может случиться, это 1,3,4,2 — это вы должны обработать в своем коде, то есть запомнить, кому какой объект «принадлежит»

3. насколько я видел, иногда команды не синхронизируются, если они выполняются очень быстро, похоже, что для каждой команды есть соединение, а не одно соединение, однако это не моя часть кода, с которой нужно возиться, я не эксперт… :/

4. @Greg не могли бы вы, пожалуйста, опубликовать пример того, каким должно быть содержимое метода получения и открытия? PS Я добавил промежуточное закрытие, и теперь я думаю, что tryopen пройдет, и не должен.

Ответ №1:

Вы можете использовать инструкцию lock, чтобы запретить двум потокам доступ к Open и Close. Чтобы предотвратить состояние гонки при проверке, открыто ли уже окно, мы можем изменить Open() на TryOpen() и вернуть false, если окно уже открыто. Есть способы сделать это так, чтобы нам не приходилось возвращать логическое значение, но это, вероятно, самый простой.

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

 private object locker=new object();
public bool TryOpen(Subject by, params string[] p)
{
    lock(locker)
    {
        if(this.opened)
            return false;
        this.opened = true;
        return true;
    }
}

public void Close(Subject by, params string[] p)
{   
    lock(locker)
    {
        this.opened = false;
    }
}
  

Комментарии:

1. Я знаю об операторе lock, и, кстати, он предназначен не для предотвращения одновременного открытия closing, а для предотвращения открытия окна, которое получает другой пользователь. Однако я думаю, что я недостаточно прояснил себя. Я отредактирую сообщение, чтобы отразить это.

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

3. хорошо, я думаю, что теперь я хорошо объяснил, чего я пытаюсь достичь, извините за путаницу. теперь со всеми 6 правилами все завершено.

4. нелегко хорошо объяснить, чего я хочу достичь, и английский — не мой язык… но, тем не менее, я поставил вам лайк за ваши усилия 🙂

Ответ №2:

Мое решение для этого:

 Monitor.TryEnter(obj)
  

Похоже на блокировку, но не блокирует.

Таким образом, мы блокируем объект, когда пользователь начинает его использовать,

и при открытии окна сделайте проверку, которая гласит:

 if (this.subject == subject || Monitor.TryEnter(obj))
  

Комментарии:

1. я думаю, это было результатом неправильного редактирования. Это не имело никакого смысла.