Страницы

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

воскресенье, 29 декабря 2019 г.

Принудительное выполнение участка кода из главного потока приложения [дубликат]

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


        
             
                
                    
                        
                            This question already has answers here:
                            
                        
                    
                
                        
                            Работа с контролами из фонового потока
                                
                                    (2 ответа)
                                
                        
                                Закрыт 4 года назад.
            
                    
В приложении используются backgroundworker'ы для решения некоторых задач. Подскажите,
как правильно сделать так, чтобы определенный фрагмент кода всегда выполнялся только
из главного потока приложения, даже при обращении к ним из потока worker'a?    


Ответы

Ответ 1



Для того чтобы возможно было выполнить код в некотором потоке, необходимо, чтобы сам этот поток был организован определённым образом, а именно в виде цикла обработки сообщений. В случае с WinAPI это достигается использованием цикла GetMessage - DispatchMessage. Если не использовать WindowsForms, подобный цикл можно организовать с помощью класса EventWaitHandle. private readonly EventWaitHandle m_signal = new EventWaitHandle(false, EventResetMode.AutoReset); private readonly Queue m_events = new Queue(); private volatile bool m_stop = false; public void Start() { // Создаём поток синхронизации. Все события будут обрабатываться в нём var th = new Thread(this.Run); th.Start(); } private void Run() { // Ожидаем сигнала о новом событии while (m_signal.WaitOne(Timeout.Infinite)) { // Если пришёл сигнал о прекращении работы - выходим if (m_stop) break; // Пока обрабатываются сообщения из очереди, добавлять их нельзя lock(m_events) { while (m_events.Count > 0) { try { // Достаём из очереди делегат и выполняем его m_events.Dequeue()(); } catch(Exception ex) { // Как-нибудь обрабатываем внешнее исключение } } } } // Освобождаем ресурсы m_signal.Close(); } public void Invoke(Action action) { if (action == null) throw new ArgumentNullException("action"); // Добавляем событие в очередь и инициируем событие // Пока добавляем событие, приостанавливаем обработку lock(m_events) { m_events.Enqueue(action); m_signal.Set(); } } public void Stop() { // Инициируем сигнал о прекращении работы lock(m_events) { m_stop = true; m_signal.Set(); } }

Ответ 2



Думаю, что стоит привести ещё один пример, на этот раз - как использовать интерфейс ISyncronizeInvoke. Этот интерфейс реализован в классе System.Windows.Forms.Control и предназначен специально для прокидывания любых делегатов в поток обработки цикла сообщений, в случае с контролами - цикла сообщений Windows. Интерфейс содержит 4 члена. Свойство InvokeRequired возвращает true, если текущий поток выполнения не является потоком обработки цикла сообщений. Если мы уже в потоке обработки цикла сообщений, свойство возвращает false. Метод Invoke добавляет переданный ему делегат в цикл обработки сообщений и блокирует текущий поток до тех пор, пока цикл обработки сообщений не выполнит этот делегат. После того как делегат выполнен, метод возвращает управление со значением, которое вернул переданный ему делегат в процессе выполнения. Если делегат имел тип возвращаемого значения void, метод возвращает null. Метод BeginInvoke добавляет переданный ему в качестве параметра делегат в цикл обработки сообщений и немедленно возвращает управление. Он возвращает объект типа IAsyncResult, который можно передать в качестве параметра последнему методу EndInvoke для ожидания завершения асинхронной операции. Простейший цикл обработки сообщений, который я привёл в предыдущем примере, обрабатывает события именно асинхронно, возвращая управление, не дожидаясь выполнения добавленного в очередь метода. Для того чтобы добавить элемент в коллекцию так, чтобы само добавление обязательно происходило в потоке пользовательского интерфейса вне зависимости от того, откуда оно было вызвано, следует использовать такой паттерн: private readonly ISynchronizeInvoke m_invoker; private readonly BindingList m_entries = new BindingList(); // Конструктор с Dependency Injection, которому передаётся контрол либо форма public BackgroundHandler(ISynchronizeInvoke invoker) { if (invoker == null) throew new ArgumentNullException("invoker"); m_invoker = invoker; } // public bool Add(MyEntry entry) { // Если мы не в потоке обработки сообщений, // метод добавляет в очередь сообщений ссылку на самого себя, // ждёт завершения обработки и возвращает управление if (m_invoker.InvokeRequired) return (bool)invoker.Invoke(new Func(this.Add), new object[] { entry }); // Поскольку мы сюда попали, мы уже в потоке обработки сообщений, // делаем полезную работу - добавляем переданный элемент в список. // Возвращаемое значение - чисто для демонстрации, как его возвращать m_entries.Add(entry); return true; } Помещение кода синхронизации прямо в начало метода перед полезной работой очень облегчает читабельность кода, если полезная работа содержит много логики.

Ответ 3



Принудительно выполнить код в другом потоке нельзя - можно только передать управление другому потоку для выполнения этого (или другого) кода. Что и демонстрируют все примеры приведенные в ответах на вопрос. Функция (или "участок кода") вызванная в одном потоке не может перейти в другой поток. Возможны только следующие паттерны: приостановить выполнение потока на этом участке кода пока он не выполниться в другом потоке (критические секции или семафоры) пропуск участка кода для некоторых потоков (реализуется разными способами) передача управления в другой поток с приостановкой (или без приостановки) выполнения данного потока (евенты, мъютексы) Приведенные примеры Modus'а вариации третьего пункта списка + цикла сообщений windows. Но не ответ на вопрос.

Ответ 4



Вы, видимо, не понимаете, как выполняются многопоточные приложения. Например, есть два потока: 1-й поток: работает функция F1(), вызывает F2() 2-й поток: работает функция G1(), вызывает F2() В "общей" функции F2() требуется некоторая (какая именно зависит от функции) синхронизация, но поток "поменяться" с 2-го на 1-ый никак не может. Контекст другой.

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

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