Здравствуйте, Sharov, Вы писали: S> тыц
Спасибо, но это, кажется, несколько не то, что мне нужно...
Про BackgroundWorker, Invoke и BeginInvoke я в курсе, но они вызываются из формы, а мне надо доступ к форме снаружи.
Re[2]: Как вызвать метод формы из родительского потока?
Здравствуйте, Danchik, Вы писали:
D>Здравствуйте, mDmitriy, Вы писали:
D>[Skip]
D>>На методе Dispose, естественно, сваливается в Exception. D>>Как сделать правильно?
D>Вот так:
using — это гениально, спасибо!
а в чем смысл этих строчек?
Здравствуйте, mDmitriy, Вы писали:
D>Здравствуйте, Danchik, Вы писали:
D>>Здравствуйте, mDmitriy, Вы писали:
D>>[Skip]
D>>>На методе Dispose, естественно, сваливается в Exception. D>>>Как сделать правильно?
D>>Вот так: D>using — это гениально, спасибо! D>а в чем смысл этих строчек? D>
Interrupt тут мало поможет (недоглядел)
Идея в том чтобы мы таки дождались закрытия потока, а оно произойдет только когда вы закроете форму.
Так как я не знаю сценарий использования вашего Dispose, то предложил такой. Больше конкретики и можна подогнать под ваши потребности.
Re[4]: Как вызвать метод формы из родительского потока?
S>Прерываем поток, и ждем пока он завершится.
Меня смутила эта строчка в MSDN про Interrupt : Прерывает работу потока, находящегося в состоянии WaitSleepJoin.
У меня-то поток с формой не блокирован, он активен.
Re[4]: Как вызвать метод формы из родительского потока?
D>Interrupt тут мало поможет (недоглядел) D>Идея в том чтобы мы таки дождались закрытия потока, а оно произойдет только когда вы закроете форму. D>Так как я не знаю сценарий использования вашего Dispose, то предложил такой. Больше конкретики и можна подогнать под ваши потребности.
Поток с формой запускается при активизации COM+ объекта и отображает иконку в трее.
При удалении COM+ объекта надо бы форму высвободить — по идее, это должно привести к закрытию потока (ибо Application.Run).
С чем, собственно, и был связан мой первоначальный вопрос.
Re: Как вызвать метод формы из родительского потока?
Здравствуйте, 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... ну, попробуй сделать так и сам все увидишь :
Тут еще один нюанс, который состоит в том, что вызвать Invoke или BeginInvoke до создания хэндла окна Windows Forms не даст, а кинет исключение. Для создания хэндла окна надо, кстати, немного прокачать очередь сообщений. То есть исключение возникнет не в "new Thread" (не в RunApp), а в ThreadUIClass1.Dispose при вызове _myForm.Invoke. То есть компонент будет задиспоужен с исключением, а форма (если ты обработаешь это исключение) останется существовать. Попробуй еще так и увидишь:
Короче, если интересно, можем обсудить, как правильно решать эту Проблему №2. К примеру, в COM специально для решения этой задачи существует single-threaded apartment — STA апартмент, который позволяет исполнять внешние вызовы последовательно. COM сам осуществляет синхронизацию внешних параллельных вызовов в последовательные. Конкретно эта модель апартмента аналогична окну, и даже представляет собой один поток и ту же самую очередь сообщений. И почему бы не использовать одну и ту же очередь сообщений, один и тот же рабочий поток для двух дел одновременно? Для обработки внешних вызовов, и для реакции на события окна? Этот апартмент специально для управления окнами и создавался. Минус в том, что ты потеряешь для своего компонента возможность вызова его методов в параллель, а обработка событий окна будет тормозить обработку внешних вызовов. Если у тебя стоит задача не формой управлять, а параллельно обслуживать большое число клиентов, а форма служит лишь для индикации работы, конечно решение с STA не подойдет, хотя...
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;
}
}
}
Form.Close последним делом вызывает Form.Dispose, и отличается от Form.Dispose только логикой вопрошания разрешения закрытия (подразумевает возможность отказа), закрытие всяких связанных окон, и поэтому получается масло масляное
Re[5]: Как вызвать метод формы из родительского потока?
Здравствуйте, 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("Сервер: удаленный клиент отсоединен.");
}
}
}
}
Здравствуйте, Fortnum, Вы писали:
F>А почему ты хочешь управлять формой из компонента? На мой взгляд — это не самое лучшее решение в плане архитектуры. Наверняка, ты еще из этой формы своим компонентом управляешь, или периодически мониторишь его состояние. Короче, у тебя, подозреваю, сейчас двунаправленная логическая связь "компонент"<->"форма". Гораздо лучше сделать так, чтобы либо компонент ничего не знал о форме, либо форма ничего не знала о компоненте. Т.к. в форме, не знающей ничего о компоненте, смысла нет, то получается, надо сделать так: "компонент"<-"форма". Т.е. компонент ничего не знает о форме, а форма знает о компоненте. Более того, форма для компонента, по сути, — тот же клиент.
Не совсем так...
У меня все управление формой компонентом сводится к создать/удалить, т.е., компонент о форме не может не знать, т.к., управляет процессом он, а форма лишь отображает.
Для этого и был написан класс ThreadUIClass1.
В конструктор формы передается объект данных от компонента (в примере я этот момент "упростил") и дальше форма живет своей жизнью в своем потоке, опрашивая его по таймеру, если активна.
Весь вопрос — правильно ее отдиспозить, когда компонент завершает работу.
Re[7]: Как вызвать метод формы из родительского потока?
Здравствуйте, mDmitriy, Вы писали:
D>У меня все управление формой компонентом сводится к создать/удалить, т.е., компонент о форме не может не знать, т.к., управляет процессом он, а форма лишь отображает. D>Для этого и был написан класс ThreadUIClass1. D>В конструктор формы передается объект данных от компонента (в примере я этот момент "упростил") и дальше форма живет своей жизнью в своем потоке, опрашивая его по таймеру, если активна. D>Весь вопрос — правильно ее отдиспозить, когда компонент завершает работу.
Так пусть компонент в этом объекте данных выставляет флаг, что всё, баста, данных больше не будет. А форма при очередном обновлении сама чпокнется. И управление формой компонентом сведется только к "создать".
И я не совсем понял, зачем был написан класс ThreadUIClass1 Есть компонент, форма, разделяемый компонентом и формой объект данных от компонента. А ThreadUIClass1 какую роль играет?
Re[8]: Как вызвать метод формы из родительского потока?
Здравствуйте, Fortnum, Вы писали:
F>Здравствуйте, mDmitriy, Вы писали:
D>>У меня все управление формой компонентом сводится к создать/удалить, т.е., компонент о форме не может не знать, т.к., управляет процессом он, а форма лишь отображает. D>>Для этого и был написан класс ThreadUIClass1. D>>В конструктор формы передается объект данных от компонента (в примере я этот момент "упростил") и дальше форма живет своей жизнью в своем потоке, опрашивая его по таймеру, если активна. D>>Весь вопрос — правильно ее отдиспозить, когда компонент завершает работу.
F>Так пусть компонент в этом объекте данных выставляет флаг, что всё, баста, данных больше не будет. А форма при очередном обновлении сама чпокнется. И управление формой компонентом сведется только к "создать".
F>И я не совсем понял, зачем был написан класс ThreadUIClass1 Есть компонент, форма, разделяемый компонентом и формой объект данных от компонента. А ThreadUIClass1 какую роль играет?
ThreadUIClass1 — это оболочка над формой.
Чтобы не загромождать код самого компонента.
Re[9]: Как вызвать метод формы из родительского потока?
Здравствуйте, mDmitriy, Вы писали:
F>>И я не совсем понял, зачем был написан класс ThreadUIClass1 Есть компонент, форма, разделяемый компонентом и формой объект данных от компонента. А ThreadUIClass1 какую роль играет? D>ThreadUIClass1 — это оболочка над формой. D>Чтобы не загромождать код самого компонента.
То есть компонент создает экземпляр ThreadUIClass1, экземпляр ThreadUIClass1 создаёт форму? Если класс существует только и исключительно "для не загромождения кода", лучше использовать partial