Потоки с WinForms? - PullRequest
       1

Потоки с WinForms?

4 голосов
/ 25 ноября 2011

В моем приложении я запускаю скрытую dummyForm, которая просто создается для отслеживания основного потока пользовательского интерфейса. Таким образом, если новая форма собирается быть созданной, InvokeRequired используется в фиктивной форме, чтобы убедиться, что мы находимся в главном потоке пользовательского интерфейса при создании новой формы.

Сразу после создания формы frmStart я проверяю frmStart.InvokeRequired, и для него установлено значение false, поэтому здесь не нужно вызывать (то же самое относится и к dummyForm.InvokeRequired).

Затем я получил frmMyDialog, который будет использовать frmStart в качестве родителя / владельца примерно так:

using(Create frmMyDialog on main UI thread)
{
    frmMyDialog.Show(frmStart);
}

Это вызовет исключение перекрестной нити, и странная вещь в этом:

frmMyDialog.InvokeRequired = false
dummyForm.InvokeRequired = false
frmStart.InvokeRequired = true

И это даже когда я проверяю, что dummyForm.InvokeRequired ложно при создании frmStart?

Значение frmMyDialog.InvokeRequired всегда должно совпадать с dummyForm.InvokeRequired? Что здесь происходит?

Я проверил, что frmStart и dummyForm вообще не воссоздаются после создания первого экземпляра.

Редактировать1:

Вот как запускается приложение:

public static void Main(string[] args)
{
     _instance = new MyClientMain(parameters);
     Application.Run(_instance);
}

Конструктор класса MyClientMain запустит программу установки в статическом классе с именем MainControl. MainControler в методе установки создаст пустую форму следующим образом:

if (_dummyForm == null)
   _dummyForm = new Form();

После того, как это будет сделано, форма входа будет обрабатывать вход в систему, и эта форма многопоточная. Когда вход в систему будет завершен, MainController будет вызван снова, чтобы создать экземпляр и открыть основной MDI windo, который содержит frmStart. Чтобы убедиться, что мы находимся в одном потоке, сделаем следующее:

public static StartApplication()
{
if (_dummyForm.InvokeRequired)
                    _dummyForm.Invoke(new MethodInvoker(delegate { OpenMainOrbitWindow(); }));
     //Instanciate mainform and frmStart then open mainForm with frmStart as a MDI child

}

Здесь нет многопоточности.

Затем, когда служба переходит в автономный режим, запускается событие, и мне нужно вызвать frmMyDialog, но при использовании .ShowDialog () это диалоговое окно будет помещено за формами, так что родитель / владелец чаще всего будет найден и установлен так: *

public static Form GetActiveForm()
        {
            Form activeForm = Form.ActiveForm;

            if (activeForm != null)
                return activeForm;

            if (MainOrbitForm.TopMost)
                return MainOrbitForm;
            else
            {
                FormCollection openForms = Application.OpenForms;
                for (int i = 0; i < openForms.Count && activeForm == null; ++i)
                {
                    Form openForm = openForms[i];
                    if (openForm.IsMdiContainer)
                        return openForm.ActiveMdiChild;
                }
            }

            if (_patientForm != null)
            {
                if (_patientForm.TopMost)
                    return _patientForm;
            }

            return null;
        }

        public static string ShowOrbitDialogReName()
        {
            frmMyDialog myDialog;
            Form testForm;

            //Makes sures that the frmOrbitDialog is created with the same thread as the dummyForm
            //InvokeRequired is used for this
            using (myDialog = MainController.CreateForm<frmOrbitDialog>())
            {
               //Settings...
               testForm = GetActiveForm();
               myDialog.ShowDialog(GetActiveForm(testForm));


            }
        }

Проблема в том, что

myDialog.InvokeRequired = false
testForm.InvokeRequired = true;
MainController.DummyForm.InvokeRequired = false;

Edit2: Запускаем и создаем dummyform:

dummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9 

После успешного входа в систему мы создаем основную форму

_mainForm.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9

Пока все выглядит хорошо. Затем принимается обратный вызов (WCF), и событие создает frmMyDialog в том же потоке (Invoke используется в dummyForm), а затем используется ShowDialog:

frmMyCustomDialog.ShowDialog(_mainForm)

Это создает исключение CrossThreadException, и вот так оно выглядит на данный момент:

_mainForm.InvokeRequired = true
frmMyCustomDialog.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 12

Почему MainControl.DummyForm не соответствует действительности? ManageThreadId не 9, а 12?

Ответы [ 3 ]

2 голосов
/ 25 ноября 2011

Вы должны использовать System.Threading.SynchronizationContext.Current.Он был создан непосредственно для подобных целей.Вы можете получить к нему доступ в любом месте после создания первой формы вашего приложения.Судя по приведенному ниже примеру, это не должно быть проблемой, так как вы создаете форму прямо в начале приложения.

public static void Main(string[] args)
{
     _instance = new MyClientMain(parameters);
     Application.Run(_instance);
}

Затем в любом месте вам нужно, чтобы код выполнялся в потоке пользовательского интерфейса, вы простоиспользуйте

System.Threading.SynchronizationContext.Current.Send() // To execute your code synchronously
System.Threading.SynchronizationContext.Current.Post() // To execute your code synchronously

Помните, что SynchronizationContext достаточно уместен, чтобы увидеть, что вы вызываете его уже из потока пользовательского интерфейса, а затем он просто выполнит ваш делегат напрямую.Кроме того, помните, что вам нужно создать некоторую форму или элемент управления WinForms перед первым использованием SynchronizationContext, поскольку при этом контекст будет инициализирован для соответствующей реализации.

Существует 3 реализации: По умолчанию,это ничего не делает - просто всегда запускайте код синхронно, он остается в Current до тех пор, пока вы не создадите элемент управления WinForms или WPF.Затем Current будет заполнен либо контекстом для Winforms, либо контекстом для диспетчера WPF.

1 голос
/ 29 ноября 2011

Это просто не в моей голове, но, как сказал Владимир Перевалов в другой дискуссии, вы должны сделать свою форму видимой.

Если ваш frmDummy никогда не отображается, то у него никогда не будет создан и назначен дескриптор окна, и поэтому он всегда будет отвечать False на «InvokeRequired». Это будет означать, что весь код, который вы хотите синхронизировать через frmDummy, никогда не отправляется в исходный поток пользовательского интерфейса, а всегда выполняется в текущем потоке. (который становится собственным потоком пользовательского интерфейса для только что созданного элемента управления).

Важно отметить, что InvokeRequired пытается определить, принадлежит ли дескриптор окна указанного элемента управления другому потоку. Это не имеет ничего общего с конструктором.

Если вы не хотите показывать frmDummy, вы можете вызвать CreateControl сразу после его создания, чтобы убедиться, что ему назначен дескриптор.

0 голосов
/ 25 ноября 2011

Я не совсем понял ваш вопрос, потому что ваши примеры на самом деле ничего не показывают о многопоточности - однако, если вы хотите создать форму, где родительская форма - это другая форма из другого потока, вы можете использовать этот код:

public void CreateShowDialogForm()
{
  if (this.InvokeRequired)
  {
    this.Invoke(new Action(CreateShowDialogForm));
  }
  else
  {
    Form frmMyDialog = new Form();
    frmMyDialog.Show(this);
  }
}

private void Form4_Load(object sender, EventArgs e)
{
  Task t = new Task(() => CreateShowDialogForm());
  t.Start();
  t.ContinueWith(task => true);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...