Требуется объяснение по обработке событий и делегатам

У меня есть код, который я написал, и он делает то, что я хочу. Однако я не совсем уверен, как именно это работает. Часть, с которой у меня больше всего проблем, - это последняя часть. У меня был textBox1.Text = "test", который не работал. У меня возникла ошибка времени выполнения из-за того, что он был вызван из другого потока. Когда я поместил textBox1.Invoke (и т. Д.), Он работал, как ожидалось. Почему?

Как видите, я знаю достаточно, чтобы быть опасным, и я действительно хочу понять, что здесь происходит, вместо того, чтобы слепо копировать и вставлять данные с сайтов в Интернете.

У меня в классе SerialCommunicator есть следующее:

public SerialCommunicator(SerialPort sp)
{
    this.sp = sp;
    sp.ReceivedBytesThreshold = packetSize;
    sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
    sp.Open();
}    
public void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(50);
    SerialPort s = (SerialPort)sender;
    byte[] buffer = new byte[128];
    s.Read(buffer, 0, s.BytesToRead);
}

Затем в моем Form1.cs у меня есть кнопка, которая при нажатии выполняет следующие действия:

private void btnPortOK_Click(object sender, EventArgs e)
{
    string comPort = cboComPorts.SelectedItem.ToString();
    SerialPort sp = new SerialPort(comPort, 9600, Parity.None, 8, StopBits.One);
    sp.DataReceived += new SerialDataReceivedEventHandler(DataHasBeenReceived);
    comm = new SerialCommunicator(sp);
}
public void DataHasBeenReceived(object sender, EventArgs args)
{
    textBox1.Invoke(new EventHandler(delegate { textBox1.Text += "test"; }));
}

person Pete    schedule 23.02.2011    source источник


Ответы (5)


Я не эксперт в этом (наверняка будут ответы получше). Но насколько я понимаю, поток GUI "владеет" вашей формой. Поэтому, когда вы пытаетесь обновить его из другого потока, вы пересекаете потоки.

Invoke - это способ попросить поток графического интерфейса пользователя запустить метод. Метод, который он запускает, - ваш textBox1.Text += "test";

Идея состоит в том, чтобы вызвать делегата, который попросит поток графического интерфейса внести изменение, а не просто изменить значение самостоятельно. Это позволяет сделать изменение потокобезопасным способом.

Вот хорошая статья Джона Скита по этому поводу: http://www.yoda.arachsys.com/csharp/threads/winforms.shtml.

person Vaccano    schedule 23.02.2011
comment
+1 за ссылку. Я приму этот ответ или любой ответ, который объясняет часть, переданную в Invoke (мне кажется, что invoke принимает делегат в соответствии с MSDN, но здесь ему передается тип EventHandler, который имеет делегат в качестве параметра). Как видите, я хочу что-то сделать, когда данные поступают на последовательный порт. Можно ли написать textBox1.Invoke (и т. Д.) Проще? - person Pete; 23.02.2011
comment
Control.Invoke принимает делегата. EventHandler - это тип делегата (документы здесь: msdn.microsoft.com/en- us / library / db0etb8x.aspx), так что он может взять это, если хотите. Или вы можете просто передать лямбда-выражение (приведенное как делегат) без оболочки EventHandler. Самый сжатый способ, который я могу придумать для написания вашего кода, был бы таким: textBox1.Invoke((Action)(()=> textBox1.Text += "test")); - person Vaccano; 23.02.2011
comment
Джон Скит показывает, как можно использовать метод расширения, если вы хотите избежать приведения здесь: stackoverflow.com/questions/411579/ - person Vaccano; 23.02.2011

Это привязка к потоку. Элементы управления пользовательского интерфейса не любят, когда к ним прикасаются ничто, кроме создавшего их потока, но поток DataReceived происходит из другого потока. Добавление вызова кControl.Invoke возвращает элемент работы в поток пользовательского интерфейса, поэтому обновление Text может быть успешным.

person Marc Gravell    schedule 23.02.2011

События вызываются из потока, в котором они происходят. (Если не указано иное).

Подумайте об этом так:
Когда вы активируете событие, оно фактически называется функцией EventName (). Таким образом, вызов события означает фактически переход ко всем методам, которые были зарегистрированы для этого события, и выполнение их.
Но это делается в том же потоке последовательным способом.

Поэтому, если событие произошло в потоке, который не является вашим потоком пользовательского интерфейса, вы получите эту ошибку.

person Yochai Timmer    schedule 23.02.2011

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

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

person Anders Zommarin    schedule 23.02.2011

textBox1.Text = "test" не работает, потому что вы вызываете его из другого потока (т.е. события DataHasBeenReceived), а затем из потока, которому принадлежит текстовое поле. Обычно это поток, в котором работает ваше приложение и который создает ваш графический интерфейс (и, следовательно, ваше текстовое поле). Invoke работает, потому что эти методы переключаются на поток графического интерфейса, устанавливают текст и затем переключается обратно на поток вашего DataHasBeenReceived события.

В Net 1.0 и 1.1 вы могли использовать элементы управления с графическим интерфейсом пользователя из другого потока, а затем из того, которому они принадлежали, но это приводило к множеству проблем, когда потоки одновременно начинали обращаться к элементам управления. Итак, начиная с версии 2.0, Microsoft изменила это.

Если вы хотите знать, нужно ли использовать invoke или нет (т. Е. Можно ли вызывать метод как из потока графического интерфейса, так и из другого потока), вы можете использовать свойство InvokeRequired в сочетании с if else. Вызов вызова немного дороже, чем прямая манипуляция с элементом управления.

person Sem Vanmeenen    schedule 23.02.2011