Изменение свойства класса из другого потока в C#

#c# #multithreading #sockets

#c# #многопоточность #сокеты

Вопрос:

У меня есть класс C #, который выполняет бесконечный цикл до тех пор, пока условной переменной не будет присвоено значение true. Есть другой класс, который ожидает сетевого сообщения, и когда сообщение получено, вызывается другой класс для изменения условной переменной на true, чтобы он мог выйти из цикла while. Ожидание сообщения выполняется в отдельном потоке:

Класс-модификатор:

 public class Modifier{
Otherclass log;
private static NetworkStream theStream;
private StreamReader theInput;

public Modifier(Otherclass other, NetworkStream str)
            {
                this.log = other;
                theStream = str;
                theInput = new StreamReader(theStream);
                Thread listenThread = new Thread(new ThreadStart(listen));
                listenThread.Start();
            }

            public void listen()
            {
                while (true)
                {
                    log.postMessage(theInput.ReadLine());
                }
            }
}
  

И другой класс:

 public class Otherclass{
    bool docontinue = true;
    public void postMessage(string input)
    {
         docontinue = true;
    }

    public void wait()
    {
          while(!docontinue)
          {
          }
    }
}
  

Проблема в том, что программа застревает на while (!docontinue), хотя сообщение отправлено. Я подозреваю, что проблема в том, что переменная docontinue не изменяется, но я не знаю, кроется ли проблема где-то в другом.

Ответ №1:

Здесь возникают различные проблемы —

Первый и прямой ответ на ваш вопрос заключается в том, что вам нужно объявить ваше логическое поле, используя volatile:

 private volatile bool doContinue = true;
  

При этом иметь цикл, который выполняет цикл while без тела, очень плохо — он будет использовать 100% процессора в этом потоке и просто «вращаться» бесконечно.

Гораздо лучший подход к подобным ситуациям — заменить цикл while на WaitHandle, такой как ManualResetEvent. Это позволяет вам дождаться события сброса и блокировать, пока вы не будете готовы продолжить. Вы вызываете Set() для него в другом потоке, чтобы разрешить продолжение выполнения.

Например, попробуйте это:

 public class Otherclass{
    ManualResetEvent mre = new ManualResetEvent(false);

    public void PostMessage(string input)
    {
         // Other stuff here...
         mre.Set(); // Allow the "wait" to continue
    }    

    public void Wait()
    {
          mre.WaitOne(); // Blocks until the set above
    }
}
  

Ответ №2:

Здесь у вас есть два (потенциально) бесконечных цикла. И на самом деле ничто никогда не вызывает Wait()

Есть ли веская причина, по которой вам нужно тратить циклы в фиктивном цикле внутри метода wait? Какой цели это служит?

Мне кажется, postMessage должен запустить новый поток, который выполнит всю необходимую работу после того, как функция Wait() должна прерваться.

Ответ №3:

Вы можете использовать Volatile

 private volatile bool docontinue = true;
  

Ответ №4:

Попробуйте добавить поток.Переход в спящий режим (100) в вашем цикле. Также рассмотрите возможность использования класса ManualResetEvent.

ОБНОВЛЕНИЕ: я только что проверил, wait() завершается даже без потока.Режим ожидания, изменчивость и другие вещи. Но мое тестовое консольное приложение зависает, потому что функция listen() никогда не заканчивается…

Ответ №5:

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

 public class Otherclass{
    bool docontinue = true;
    public void postMessage(string input)
    {
         docontinue = true;
    }

    public void wait()
    {
          while(!docontinue)
          {
          }
    }
}
  

docontinue значения не изменяются. Оно начинается как true, и вы устанавливаете его в true при отправке сообщения. Кроме того, у вас есть not в вашем предложении while, поэтому цикл никогда не должен выполняться, так как !docontinue всегда имеет значение false .