Страницы

Поиск по вопросам

понедельник, 23 декабря 2019 г.

Получить значение из родительского потока

#c_sharp


Есть поток родительский который main программы и создан дочерний. 
В родительском есть меняющееся переменная. Дочерний должен время от времени получать
значение этой переменной. Каким образом?

while (true) {
  // вот тут надо получить значение родительской переменной int pParam
  // и записать в дочернюю переменную int cParam
  Thread.Sleep(1000);
}

    


Ответы

Ответ 1



Операции с int - атомарны. Так что просто присваиваем в родительском потоке, читаем в дочернем. И никаких проблем.

Ответ 2



Для случая «общения» потоков в GUI-программе обычно используется встроенное в GUI-фреймворк средства выполнения кода в UI-потоке. Для WinForms это метод Control.Invoke, который нужно вызывать в фоновом потоке, а он выполняет переданный ему делегат в UI-потоке. При этом вы должны обращаться к контролам только из UI-потока, то есть, изнутри аргумента Control.Invoke. Явная синхронизация при этом не нужна, т. к. вызов Invoke сам является синхронизирующим. Пример считывания значения поля класса или свойства контрола из фонового потока: void Run() // фоновый поток { // ... что-то делаем int cParam; Invoke((MethodInvoker) delegate { cParam = pParam; }) // пользуемся ... } Этот код для случая, когда метод, который бежит в потоке, является методом самой формы. Если нет, нужно сответственно form.Invoke и form.pParam. Для коммуникации между фоновыми потоками, или в случае не-GUI-программы всё сложнее. Если у вас есть разделяемые между потоками данные, вам необходимо при чтении и записи из использовать lock. Размер данных и «атомарность» не имеет значения, ведь оптимизатор вправе считать, что данные не меняются, если он не видит lock и запись в данном потоке (и тогда он имеет право закешировать переменную в регистре, например). Если вам нужен поллинг значения из другого потока, это обычно организуется так: class C { int v; object vlock = new object(); // для чтения void access() { bool iszero; lock (vlock) iszero = (v == 0); if (iszero) ... } // для записи void increment() { lock (vlock) v++; } } Не пытайтесь «сэкономить», пытаясь избежать синхронизации через lock. В подавляющем большинстве случаев в вашей программе есть на несколько порядков более расходный код, рядом с которым одни несчастный lock просто пылинка. Например, если ваша программа читает файлы или загружает данные из сети. Впрочем, обязательно спрофилируйте, говорить об оптимизации без профилирования абсурд. Есть ещё немного более «дешёвые» примитивы синхронизации (volatile, memory barrier и тому подобное), которые могут помочь при определённых условиях, если вы хорошо понимаете тонкости модели памяти. Без хороших знаний по теме я бы не рекомендовал пытаться ими воспользоваться. К сожалению, спецификация языка рассказывает о lock и его «соседях» в достаточно малопонятных терминах, поэтому приходится доверять мнению разработчиков языка. Процитирую статью Эрика Липперта, одного из ключевых разработчиков языка C# (перевод мой): Если честно, я вообще не рекомендую вам пользоваться volatile-полями. Volatile-поле означает, что вы делаете что-то совершенно безумное: вы пытаетесь модифицировать и считывать поле в различных потоках без lock'а. Конструкция lock даёт гарантию, что память, прочитанная или изменённая внутри неё будет консистентной, lock гарантирует, что только один поток будет иметь доступ к данному участку памяти в данный момент времени и так далее. Количество случаев, когда lock слишком медленный, исчезающе мало, и вероятность того, что ваш код будет неправильным, потому что вы не понимаете подробности модели памяти, весьма велика. Я не пытаюсь избегать lock'ов никогда, кроме самых тривиальных случаев использования Interlocked-операций. Оставьте volatile для настоящих экспертов. (Я лично считаю совет использовать volatile вредоносным.) Дополнительное чтение по теме: We need to lock a .NET Int32 when reading it in a multithreaded code? Volatile keyword in C# – memory model explained Atomicity, volatility and immutability are different, part three

Ответ 3



Операции с int - атомарны, так что единственное, что может пойти не так - оптимизатор может закешировать значение вашей переменной, считая (по умолчанию), что работа с ней происходит из одного потока. Для отключения "однопоточных" оптимизаций служит ключевое слово volatile: Ключевое слово volatile указывает, что поле может быть изменено несколькими потоками, выполняющимися одновременно. Поля, объявленные как volatile, не проходят оптимизацию компилятором, которая предусматривает доступ посредством отдельного потока. Это гарантирует наличие наиболее актуального значения в поле в любое время. Про наиболее актуальное значение в MSDN немного обманывают - по сути, установка volatile запретит рантайму переиспользовать значение поля, вычитанное при прошлом к нему обращении. ECMA-335, I.12.6.7 Volatile reads and writes: An optimizing compiler that converts CIL to native code shall not remove any volatile operation, nor shall it coalesce multiple volatile operations into a single operation. Вместо использования volatile можно пооборачивать доступ к переменным в lock. Но стоит учитывать, что утверждение "lock косвенно отключает кэширование" закопано глубоко в дебри спецификации (спецификации CLI, а не C#), Срабатывает это отключение за счет того, что вход в lock сам по себе считается volatile read. Что запрещает оптимизатору выносить чтение памяти наверх из лока. Т.е. если вы откроете справку по lock, то найдете только Ключевое слово lock не позволит ни одному потоку войти в важный раздел кода в тот момент, когда в нем находится другой поток. Ни слова про кэширование. И следующий разработчик на проекте, знающий про lock, но не знающий про volatile, удалит ваши локи со словами "int32 читается атомарно, понаставили тут ненужных блокировок" :)

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

Отправить комментарий