#c_sharp #winforms #многопоточность
В отдельных потоках происходит вычисление, результат которых надо вывести в форму. Для передачи данных в UI-поток надо вызывать Control.BeginInvoke using System.Windows.Forms; using System.Threading.Tasks; void addText(Form f, int res) { f.Controls.Add(new TextBox() { Text = res.ToString(), Dock = DockStyle.Top }); } var f = new Form(); f.Load += (s, e) => { Task.Run(() => { for(var result=0; result < 5; result++) f.BeginInvoke(new Action(() => addText(f, result) )); }); }; f.ShowDialog(); Но в форме выводятся неправильные значения. Во всех TextBox'ах выводится 5. Вызываю BeginInvoke из UI-потока f.Load += (s, e) => { var result = "a"; f.BeginInvoke(new Action(() => addText(f, result) )); result = "b"; f.BeginInvoke(new Action(() => addText(f, result) )); }; Но в форме также выводятся неправильные значения. В TextBox'ах выводится "b". Почему так и как вывести правильный результат в форму? Возможно ли избавиться от вызовов BeginInvoke в разных частях кода?
Ответы
Ответ 1
Альтернативное решение той же проблемы - не использовать одну и ту же переменную для хранения результатов двух разных операций: f.Load += (s, e) => { var resultA = "a"; f.BeginInvoke(new Action(() => addText(f, resultA) )); var resultB = "b"; f.BeginInvoke(new Action(() => addText(f, resultB) )); };Ответ 2
Вызов BeginInvoke ставит делегат в специальную очередь, которая обрабатывается в UI-потоке. К моменту вызова делегата, в result оказывается новое значение, а не то, которое было в момент вызова BeginInvoke. Если в addText надо передать result, существующий на момент вызова BeginInvoke, то строку f.BeginInvoke(new Action(() => addText(f, result) )); надо заменить на следующую f.BeginInvoke( new Action(v => addText(f, v)), result.ToString() // передается в v в момент вызова делегата ); Т.к. f это ссылка на Form, и мы знаем, что она не изменится, то передавать ее в BeginInvoke не надо. При работе с замыканиями надо соблюдать правило: переменные, которые могут измениться к моменту вызова делегата, должны быть переданы в метод BeginInvoke при его вызове. Вместо того, чтобы вызывать BeginInvoke в разных частях кода можно перенести BeginInvoke в метод addText и вызывать его после проверки InvokeRequired. void addText(Form frm, int res) { if (frm.InvokeRequired) // true - если вызван не из UI-потока frm.BeginInvoke( new Action
Комментариев нет:
Отправить комментарий