Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 17.09.14 10:33
Оценка:
Всем привет!

Есть примерно такой вот код (.NET 4.0):
public class ThreadUIClass1 : IDisposable
{
    private Thread _thread;
    private Form _myForm;

    public void RunThread()
    {
        _thread = new Thread(RunApp);
        _thread.Start();
    }

    private void RunApp(object obj)
    {
        _myForm = new Form();
        Application.Run(_myForm);
    }

    public void Dispose()
    {
        try
        {
            if (_myForm != null)
            {
                _myForm.Dispose();
            }
        }
        catch (Exception e)
        {
        }
    }
}

На методе Dispose, естественно, сваливается в Exception.
Как сделать правильно?
Re: Как вызвать метод формы из родительского потока?
От: Sharov Россия  
Дата: 17.09.14 11:00
Оценка: 2 (1) +1
Здравствуйте, mDmitriy, Вы писали:

D>Как сделать правильно?


тыц
Кодом людям нужно помогать!
Re: Как вызвать метод формы из родительского потока?
От: Danchik Украина  
Дата: 17.09.14 12:19
Оценка: 3 (1)
Здравствуйте, mDmitriy, Вы писали:

[Skip]

D>На методе Dispose, естественно, сваливается в Exception.

D>Как сделать правильно?

Вот так:

    public class ThreadUIClass1 : IDisposable
    {
        private Thread _thread;

        public void Dispose()
        {
            if (_thread != null)
            {
                _thread.Interrupt();
                _thread.Join(Int32.MaxValue);
                _thread = null;
            }
        }

        public void RunThread()
        {
            _thread = new Thread(RunApp);
            _thread.Start();
        }

        private void RunApp(object obj)
        {
            using (var myForm = new Form())
            {
                Application.Run(myForm);
            }
        }
    }
Re[2]: Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 17.09.14 13:12
Оценка:
Здравствуйте, Sharov, Вы писали:
S> тыц
Спасибо, но это, кажется, несколько не то, что мне нужно...
Про BackgroundWorker, Invoke и BeginInvoke я в курсе, но они вызываются из формы, а мне надо доступ к форме снаружи.
Re[2]: Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 17.09.14 13:21
Оценка:
Здравствуйте, Danchik, Вы писали:

D>Здравствуйте, mDmitriy, Вы писали:


D>[Skip]


D>>На методе Dispose, естественно, сваливается в Exception.

D>>Как сделать правильно?

D>Вот так:

using — это гениально, спасибо!
а в чем смысл этих строчек?
_thread.Interrupt();
_thread.Join(Int32.MaxValue);
Re[3]: Как вызвать метод формы из родительского потока?
От: Sharov Россия  
Дата: 17.09.14 13:29
Оценка:
Здравствуйте, mDmitriy, Вы писали:

D>а в чем смысл этих строчек?

D>
D>_thread.Interrupt();
D>_thread.Join(Int32.MaxValue); 
D>


Прерываем поток, и ждем пока он завершится.
Кодом людям нужно помогать!
Re[3]: Как вызвать метод формы из родительского потока?
От: Danchik Украина  
Дата: 17.09.14 13:40
Оценка:
Здравствуйте, mDmitriy, Вы писали:

D>Здравствуйте, Danchik, Вы писали:


D>>Здравствуйте, mDmitriy, Вы писали:


D>>[Skip]


D>>>На методе Dispose, естественно, сваливается в Exception.

D>>>Как сделать правильно?

D>>Вот так:

D>using — это гениально, спасибо!
D>а в чем смысл этих строчек?
D>
D>_thread.Interrupt();
D>_thread.Join(Int32.MaxValue); 
D>


Interrupt тут мало поможет (недоглядел)
Идея в том чтобы мы таки дождались закрытия потока, а оно произойдет только когда вы закроете форму.
Так как я не знаю сценарий использования вашего Dispose, то предложил такой. Больше конкретики и можна подогнать под ваши потребности.
Re[4]: Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 17.09.14 15:04
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, mDmitriy, Вы писали:


D>>а в чем смысл этих строчек?

D>>
D>>_thread.Interrupt();
D>>_thread.Join(Int32.MaxValue); 
D>>


S>Прерываем поток, и ждем пока он завершится.

Меня смутила эта строчка в MSDN про Interrupt : Прерывает работу потока, находящегося в состоянии WaitSleepJoin.
У меня-то поток с формой не блокирован, он активен.
Re[4]: Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 17.09.14 15:12
Оценка:
Здравствуйте, Danchik, Вы писали:
D>>>Вот так:
D>>а в чем смысл этих строчек?
D>>
D>>_thread.Interrupt();
D>>_thread.Join(Int32.MaxValue); 
D>>

D>Interrupt тут мало поможет (недоглядел)
D>Идея в том чтобы мы таки дождались закрытия потока, а оно произойдет только когда вы закроете форму.
D>Так как я не знаю сценарий использования вашего Dispose, то предложил такой. Больше конкретики и можна подогнать под ваши потребности.
Поток с формой запускается при активизации COM+ объекта и отображает иконку в трее.
При удалении COM+ объекта надо бы форму высвободить — по идее, это должно привести к закрытию потока (ибо Application.Run).
С чем, собственно, и был связан мой первоначальный вопрос.
Re: Как вызвать метод формы из родительского потока?
От: Rinbe Россия  
Дата: 17.09.14 16:13
Оценка: 3 (1)
Здравствуйте, mDmitriy, Вы писали:

D>Всем привет!


D>Есть примерно такой вот код (.NET 4.0):

D>
D>public class ThreadUIClass1 : IDisposable
D>{
D>    private Thread _thread;
D>    private Form _myForm;

D>    public void RunThread()
D>    {
D>        _thread = new Thread(RunApp);
D>        _thread.Start();
D>    }

D>    private void RunApp(object obj)
D>    {
D>        _myForm = new Form();
D>        Application.Run(_myForm);
D>    }

D>    public void Dispose()
D>    {
D>        try
D>        {
D>            if (_myForm != null)
D>            {
D>                _myForm.Dispose();
D>            }
D>        }
D>        catch (Exception e)
D>        {
D>        }
D>    }
D>}
D>

D>На методе Dispose, естественно, сваливается в Exception.
D>Как сделать правильно?

Может вот так надо:

  public class ThreadUIClass1 : IDisposable
    {
        private Thread _thread;
        private Form _myForm;

        public void RunThread()
        {
            _thread = new Thread(RunApp);
            _thread.Start();
        }

        private void RunApp(object obj)
        {
            _myForm = new MainForm();
            Application.Run(_myForm);
        }

        public void Dispose()
        {
            Application.Exit();
        }
    }

Re[2]: Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 18.09.14 04:22
Оценка:
Здравствуйте, Rinbe, Вы писали:

R>Может вот так надо:


R>
R>        public void Dispose()
R>        {
R>            Application.Exit();
R>        }
R>

R>
Идея хорошая!
А это не схлопнет родительский поток более раньше чем надо?
Re[3]: Как вызвать метод формы из родительского потока?
От: Fortnum  
Дата: 18.09.14 08:58
Оценка: 3 (1)
Здравствуйте, mDmitriy, Вы писали:

D>Здравствуйте, Sharov, Вы писали:

S>> тыц
D>Спасибо, но это, кажется, несколько не то, что мне нужно...
D>Про BackgroundWorker, Invoke и BeginInvoke я в курсе, но они вызываются из формы, а мне надо доступ к форме снаружи.

Абсолютно без разницы, откуда вызываются Invoke и BeginInvoke. В случае с формой, откуда бы эти методы ни были бы вызваны, всё что они сделают — поставят в очередь сообщений вызов заданного делегата. Разница лишь в том, что Invoke будет ждать, когда поставленная в очередь структура данных будет обработана — т.е. будет вызван делегат, и этот делегат вернет вызов обратно. А BeginInvoke не ждет — после постановки делегата в очередь немедленно возвращает вызов. Методы Invoke и BeginInvoke — это так называемые потоко-безопасные методы, т.е. их можно вызывать из неограниченного числа потоков одновременно — они на это и рассчитаны. Вот что по этому поводу написано в MSDN :

In addition to the InvokeRequired property, there are four methods on a control that are thread safe : Invoke, BeginInvoke, EndInvoke


Вообще, говорить, что метод вызывается "из формы" в данном случае некорректно. Вызов делает поток. Т.е. в чистом виде один и тот же код может параллельно исполняться неограниченным количеством потоков. Но код сам по себе — штука бесполезная. Код изменяет состояние, и вот тут нюанс. Если каждый из этих параллельных потоков работает со своим состоянием — проблем нет. Но если они в какой-то части работают над одним общим состоянием, для каждого отдельного потока могут возникнуть непредвиденные (недетерминированные) изменения этого общего состояния между последовательно исполняемыми им инструкциями, если этот отдельный поток не рассматривает это состояние как общее, либо не имеет возможности предотвратить такие изменения.

Состояние формы для многопоточной программы — это и есть то самое общее (разделяемое) состояние. Сделать разделяемое состояние детерминированным можно как минимум двумя путями:

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

Поэтому в ThreadUIClass1.Dispose надо всего лишь написать:

_myForm.Invoke((Action)_myForm.Dispose);


Но у тебя в коде еще и не такая явная Проблема №2. В ThreadUIClass1.Dispose у тебя идет проверка "if (_myForm != null)", но поле _myForm — это разделяемое состояние между потоком "new Thread(RunApp)" и потоком, который вызовет ThreadUIClass1.Dispose. И хотя, в спецификации C# в 5.5 Atomicity of variable references говорится:

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.


То есть запись и чтение поля _myForm — атомарные операции, и само содержимое этого поля будет всегда прочитано корректно, однако, в ~0.001% может получиться так, что создание формы задержится — можно представить себе это так:

private void RunApp(object obj)
{
    var myForm = new Form();

    Thread.Sleep(10000);

    _myForm = myForm;

    Application.Run(_myForm);
}


, а клиент во время этой задержки, до назначения значения полю _myForm успеет задиспоузить твой COM+ компонент, и теоретически может получиться ситуация, когда метод ThreadUIClass1.Dispose выполнится до назначения значения полю _myForm

Более того, даже если форма успеет создаться, и значение полю _myForm будет назначено, нюанс в том, что Application.Run запускает прокачку очереди сообщений, и если ты задиспоузишь форму после ее создания, но до вызова Application.Run... ну, попробуй сделать так и сам все увидишь :

private void RunApp(object obj)
{
    _myForm = new Form();

    _myForm.Dispose();

    Application.Run(_myForm);
}


Тут еще один нюанс, который состоит в том, что вызвать Invoke или BeginInvoke до создания хэндла окна Windows Forms не даст, а кинет исключение. Для создания хэндла окна надо, кстати, немного прокачать очередь сообщений. То есть исключение возникнет не в "new Thread" (не в RunApp), а в ThreadUIClass1.Dispose при вызове _myForm.Invoke. То есть компонент будет задиспоужен с исключением, а форма (если ты обработаешь это исключение) останется существовать. Попробуй еще так и увидишь:

private void RunApp(object obj)
{
    _myForm = new Form();

    _myForm.Invoke((Action)_myForm.Dispose);

    Application.Run(_myForm);
}


Короче, если интересно, можем обсудить, как правильно решать эту Проблему №2. К примеру, в COM специально для решения этой задачи существует single-threaded apartment — STA апартмент, который позволяет исполнять внешние вызовы последовательно. COM сам осуществляет синхронизацию внешних параллельных вызовов в последовательные. Конкретно эта модель апартмента аналогична окну, и даже представляет собой один поток и ту же самую очередь сообщений. И почему бы не использовать одну и ту же очередь сообщений, один и тот же рабочий поток для двух дел одновременно? Для обработки внешних вызовов, и для реакции на события окна? Этот апартмент специально для управления окнами и создавался. Минус в том, что ты потеряешь для своего компонента возможность вызова его методов в параллель, а обработка событий окна будет тормозить обработку внешних вызовов. Если у тебя стоит задача не формой управлять, а параллельно обслуживать большое число клиентов, а форма служит лишь для индикации работы, конечно решение с STA не подойдет, хотя...
Отредактировано 18.09.2014 9:32 fortnum . Предыдущая версия . Еще …
Отредактировано 18.09.2014 9:03 fortnum . Предыдущая версия .
Отредактировано 18.09.2014 9:00 fortnum . Предыдущая версия .
Отредактировано 18.09.2014 8:59 fortnum . Предыдущая версия .
Re[5]: Как вызвать метод формы из родительского потока?
От: Danchik Украина  
Дата: 18.09.14 09:47
Оценка:
Здравствуйте, mDmitriy, Вы писали:

D>Здравствуйте, Danchik, Вы писали:

D>>>>Вот так:
D>>>а в чем смысл этих строчек?
D>>>
D>>>_thread.Interrupt();
D>>>_thread.Join(Int32.MaxValue); 
D>>>

D>>Interrupt тут мало поможет (недоглядел)
D>>Идея в том чтобы мы таки дождались закрытия потока, а оно произойдет только когда вы закроете форму.
D>>Так как я не знаю сценарий использования вашего Dispose, то предложил такой. Больше конкретики и можна подогнать под ваши потребности.
D>Поток с формой запускается при активизации COM+ объекта и отображает иконку в трее.
D>При удалении COM+ объекта надо бы форму высвободить — по идее, это должно привести к закрытию потока (ибо Application.Run).
D>С чем, собственно, и был связан мой первоначальный вопрос.

Тогда так:

    public class ThreadUIClass1 : IDisposable
    {
        private Form _form;
        private Thread _thread;

        public void Dispose()
        {
            TerminateThread();
        }

        private void TerminateThread()
        {
            if (_thread != null)
            {
                var form = _form;
                if (form != null && form.IsHandleCreated)
                    form.Invoke((MethodInvoker) (() => form.Close()));
                _thread.Join(Int32.MaxValue);
                _thread = null;
            }
        }

        public void RunThread()
        {
            _thread = new Thread(RunApp);
            _thread.Start();
        }

        private void RunApp(object obj)
        {
            _form = new Form();
            try
            {
                Application.Run(_form);
            }
            finally
            {
                _form.Dispose();
                _form = null;
            }
        }
    }
Отредактировано 18.09.2014 9:48 Danchik . Предыдущая версия .
Re[6]: Как вызвать метод формы из родительского потока?
От: Fortnum  
Дата: 18.09.14 10:02
Оценка: +1
Здравствуйте, Danchik, Вы писали:

D> var form = _form;

D> if (form != null && form.IsHandleCreated)
D> form.Invoke((MethodInvoker) (() => form.Close()));
D> _thread.Join(Int32.MaxValue);

Хм... а если _form != null, но form.IsHandleCreated еще не криэйтэд?
Re[6]: Как вызвать метод формы из родительского потока?
От: Fortnum  
Дата: 18.09.14 10:21
Оценка:
Здравствуйте, Danchik, Вы писали:

D>        private void TerminateThread()
D>        {
D>            ...
D>                if (form != null && form.IsHandleCreated)
D>                    form.Invoke((MethodInvoker) (() => form.Close()));
D>            ...
D>        }
...
D>        private void RunApp(object obj)
D>        {
D>            _form = new Form();
D>            try
D>            {
D>                Application.Run(_form);
D>            }
D>            finally
D>            {
D>                _form.Dispose();
D>                _form = null;
D>            }
D>        }
D>    }



Form.Close последним делом вызывает Form.Dispose, и отличается от Form.Dispose только логикой вопрошания разрешения закрытия (подразумевает возможность отказа), закрытие всяких связанных окон, и поэтому получается масло масляное
Re[5]: Как вызвать метод формы из родительского потока?
От: Fortnum  
Дата: 18.09.14 12:24
Оценка:
Здравствуйте, mDmitriy, Вы писали:

D>Поток с формой запускается при активизации COM+ объекта и отображает иконку в трее.

D>При удалении COM+ объекта надо бы форму высвободить — по идее, это должно привести к закрытию потока (ибо Application.Run).
D>С чем, собственно, и был связан мой первоначальный вопрос.

А почему ты хочешь управлять формой из компонента? На мой взгляд — это не самое лучшее решение в плане архитектуры. Наверняка, ты еще из этой формы своим компонентом управляешь, или периодически мониторишь его состояние. Короче, у тебя, подозреваю, сейчас двунаправленная логическая связь "компонент"<->"форма". Гораздо лучше сделать так, чтобы либо компонент ничего не знал о форме, либо форма ничего не знала о компоненте. Т.к. в форме, не знающей ничего о компоненте, смысла нет, то получается, надо сделать так: "компонент"<-"форма". Т.е. компонент ничего не знает о форме, а форма знает о компоненте. Более того, форма для компонента, по сути, — тот же клиент. Поэтому я бы предложил решить вопрос ближе к такому варианту:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        static void Main()
        {
            Console.WriteLine("Клиент: нажми любую клавишу, чтобы подключиться к серверу."); Console.ReadKey(true);

            var threadUIClass1 = Task.Run(() => new ThreadUIClass1()).Result;

            Console.WriteLine("Клиент: нажми любую клавишу, чтобы отключиться от сервера."); Console.ReadKey(true);

            Task.Run(() => threadUIClass1.Dispose());

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey(true);
        }

        public class ThreadUIClass1 : IDisposable
        {
            volatile int _data;

            volatile bool _disposed;

            public ThreadUIClass1()
            {
                new Thread(() =>
                {
                    while (!_disposed)
                    {
                        Task.Delay(100).Wait();

                        _data = new Random().Next(int.MinValue, int.MaxValue);
                    }

                    Console.WriteLine("Сервер: рабочий поток завершен."); 
                }).Start();

                Console.WriteLine("Сервер: рабочий поток запущен."); 

                new Thread(() => 
                {
                    var form = new Form();

                    form.Load += (o, e) =>
                    {
                        var timer = new System.Windows.Forms.Timer() { Interval = 5000 };

                        timer.Tick += (t, x) =>
                        {
                            form.Text = _data.ToString();

                            if (_disposed)
                            {
                                form.Dispose();
                            }
                        };

                        timer.Start();
                    };

                    Console.WriteLine("Сервер: локальный пользовательский интерфейс запущен."); 

                    Application.Run(form);

                    Console.WriteLine("Сервер: локальный пользовательский интерфейс остановлен."); 
                }).Start();
            }

            public void Dispose()
            {
                _disposed = true;

                Console.WriteLine("Сервер: удаленный клиент отсоединен."); 
            }
        }
    }
}
Отредактировано 18.09.2014 12:38 fortnum . Предыдущая версия . Еще …
Отредактировано 18.09.2014 12:26 fortnum . Предыдущая версия .
Re[6]: Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 18.09.14 13:07
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>А почему ты хочешь управлять формой из компонента? На мой взгляд — это не самое лучшее решение в плане архитектуры. Наверняка, ты еще из этой формы своим компонентом управляешь, или периодически мониторишь его состояние. Короче, у тебя, подозреваю, сейчас двунаправленная логическая связь "компонент"<->"форма". Гораздо лучше сделать так, чтобы либо компонент ничего не знал о форме, либо форма ничего не знала о компоненте. Т.к. в форме, не знающей ничего о компоненте, смысла нет, то получается, надо сделать так: "компонент"<-"форма". Т.е. компонент ничего не знает о форме, а форма знает о компоненте. Более того, форма для компонента, по сути, — тот же клиент.

Не совсем так...
У меня все управление формой компонентом сводится к создать/удалить, т.е., компонент о форме не может не знать, т.к., управляет процессом он, а форма лишь отображает.
Для этого и был написан класс ThreadUIClass1.
В конструктор формы передается объект данных от компонента (в примере я этот момент "упростил") и дальше форма живет своей жизнью в своем потоке, опрашивая его по таймеру, если активна.
Весь вопрос — правильно ее отдиспозить, когда компонент завершает работу.
Re[7]: Как вызвать метод формы из родительского потока?
От: Fortnum  
Дата: 18.09.14 13:15
Оценка:
Здравствуйте, mDmitriy, Вы писали:

D>У меня все управление формой компонентом сводится к создать/удалить, т.е., компонент о форме не может не знать, т.к., управляет процессом он, а форма лишь отображает.

D>Для этого и был написан класс ThreadUIClass1.
D>В конструктор формы передается объект данных от компонента (в примере я этот момент "упростил") и дальше форма живет своей жизнью в своем потоке, опрашивая его по таймеру, если активна.
D>Весь вопрос — правильно ее отдиспозить, когда компонент завершает работу.

Так пусть компонент в этом объекте данных выставляет флаг, что всё, баста, данных больше не будет. А форма при очередном обновлении сама чпокнется. И управление формой компонентом сведется только к "создать".

И я не совсем понял, зачем был написан класс ThreadUIClass1 Есть компонент, форма, разделяемый компонентом и формой объект данных от компонента. А ThreadUIClass1 какую роль играет?
Re[8]: Как вызвать метод формы из родительского потока?
От: mDmitriy Россия  
Дата: 18.09.14 14:10
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Здравствуйте, mDmitriy, Вы писали:


D>>У меня все управление формой компонентом сводится к создать/удалить, т.е., компонент о форме не может не знать, т.к., управляет процессом он, а форма лишь отображает.

D>>Для этого и был написан класс ThreadUIClass1.
D>>В конструктор формы передается объект данных от компонента (в примере я этот момент "упростил") и дальше форма живет своей жизнью в своем потоке, опрашивая его по таймеру, если активна.
D>>Весь вопрос — правильно ее отдиспозить, когда компонент завершает работу.

F>Так пусть компонент в этом объекте данных выставляет флаг, что всё, баста, данных больше не будет. А форма при очередном обновлении сама чпокнется. И управление формой компонентом сведется только к "создать".


F>И я не совсем понял, зачем был написан класс ThreadUIClass1 Есть компонент, форма, разделяемый компонентом и формой объект данных от компонента. А ThreadUIClass1 какую роль играет?

ThreadUIClass1 — это оболочка над формой.
Чтобы не загромождать код самого компонента.
Re[9]: Как вызвать метод формы из родительского потока?
От: Fortnum  
Дата: 18.09.14 14:45
Оценка:
Здравствуйте, mDmitriy, Вы писали:

F>>И я не совсем понял, зачем был написан класс ThreadUIClass1 Есть компонент, форма, разделяемый компонентом и формой объект данных от компонента. А ThreadUIClass1 какую роль играет?

D>ThreadUIClass1 — это оболочка над формой.
D>Чтобы не загромождать код самого компонента.

То есть компонент создает экземпляр ThreadUIClass1, экземпляр ThreadUIClass1 создаёт форму? Если класс существует только и исключительно "для не загромождения кода", лучше использовать partial
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.