недопустимая операция между потоками: доступ к элементу управления из потока, отличного от потока, в котором он был создан

<p>у меня есть сценарий. (windows forms, c #, .net)</p><ol> <li>существует основная форма, которая содержит некоторый пользовательский элемент управления.</li> <li>пользовательский элемент управления выполняет некоторую сложную операцию с данными, так что, если я напрямую вызываю метод <code>usercontrol\_load</code>, пользовательский интерфейс перестает отвечать на запросы при выполнении метода загрузки.</li> <li>чтобы преодолеть это, я загружаю данные в другой поток (пытаясь как можно меньше изменить существующий код)</li> <li>я использовал фоновый рабочий поток, который будет загружать данные и когда это будет сделано, уведомит приложение о том, что оно выполнило свою работу.</li> <li>теперь появилась настоящая проблема. весь пользовательский интерфейс (основная форма и ее дочерние элементы управления) был создан в основном основном потоке. в методе load пользовательского контроля я выбираю данные, основанные на значениях некоторого элемента управления (например, текстового поля) в usercontrol.</li> </ol><p>псевдокод будет выглядеть так:</p><p><strong>код 1</strong></p><pre><code>usercontrl1\_loaddatamethod() { if (textbox1.text == "myname") // this gives exception { //load data corresponding to "myname". //populate a globale variable list<string> which will be binded to grid at some later stage. } } </code></pre><p>это исключение было</p><blockquote> <p>недопустимая операция между потоками: доступ к элементу управления из потока, отличного от потока, в котором он был создан.</p> </blockquote><p>чтобы узнать больше об этом, я немного погуглил, и пришло предложение, например, с использованием следующего кода</p><p><strong>код 2</strong></p><pre><code>usercontrl1\_loaddatamethod() { if (invokerequired) // line #1 { this.invoke(new methodinvoker(usercontrl1\_loaddatamethod)); return; } if (textbox1.text == "myname") // now it wont give an exception { //load data correspondin to "myname" //populate a globale variable list<string> which will be binded to grid at some later stage } } </code></pre><p>но но ... кажется, я вернулся к исходной точке. приложение снова перестает отвечать на запросы. кажется, это связано с выполнением строки # 1, если условие. задача загрузки снова выполняется родительским потоком, а не третьим, который я породил.</p><p>я не знаю, понял ли я это правильно или неправильно. я новичок в потоке.</p><p>как мне решить это, а также каков эффект выполнения строки # 1, если блок?</p><p><strong>ситуация такова</strong>: я хочу загрузить данные в глобальную переменную на основе значения элемента управления. я не хочу менять значение элемента управления из дочернего потока. я не собираюсь делать это когда-либо из детской ветки.</p><p>таким образом, только доступ к значению, так что соответствующие данные могут быть получены из базы данных.</p>

<p>согласно <a href="https://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the#142108">комментарий об обновлении prerak k</a> (так как удалено):</p><blockquote> <p>я думаю, что я не представил вопрос должным образом.</p> <p>ситуация такова: я хочу загрузить данные в глобальную переменную на основе значения элемента управления. я не хочу менять значение элемента управления из дочернего потока. я не собираюсь делать это когда-либо из детской ветки.</p> <p>таким образом, только доступ к значению, так что соответствующие данные могут быть получены из базы данных.</p> </blockquote><p>решение, которое вы хотите, должно выглядеть следующим образом:</p><pre><code>usercontrl1\_loaddatamethod() { string name = ""; if(textbox1.invokerequired) { textbox1.invoke(new methodinvoker(delegate { name = textbox1.text; })); } if(name == "myname") { // do whatever } } </code></pre><p>выполните серьезную обработку в отдельном потоке <em>, прежде чем </em> попытаться вернуться к потоку элемента управления. например:</p><pre><code>usercontrl1\_loaddatamethod() { if(textbox1.text=="myname") //<<======now it wont give exception** { //load data correspondin to "myname" //populate a globale variable list<string> which will be //bound to grid at some later stage if(invokerequired) { // after we've done all the processing, this.invoke(new methodinvoker(delegate { // load the control with the appropriate data })); return; } } } </code></pre>

<h1>модель потоков в пользовательском интерфейсе</h1><p>пожалуйста, прочитайте <em> <a href="https://msdn.microsoft.com/library/ms741870(v=vs.100).aspx">threading model</a> </em> в приложениях пользовательского интерфейса, чтобы понять основные понятия. ссылка переходит на страницу, которая описывает модель потоков wpf. тем не менее, windows forms использует ту же идею.</p><h2>поток пользовательского интерфейса</h2><ul> <li>существует только один поток (поток пользовательского интерфейса), которому разрешен доступ <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.aspx">system.windows.forms.control</a> и его члены подклассов.</li> <li>попытка доступа к члену <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.aspx">system.windows.forms.control</a> из другого потока, отличного от потока пользовательского интерфейса, вызовет межпотоковое исключение.</li> <li>поскольку существует только один поток, все операции пользовательского интерфейса ставятся в очередь как рабочие элементы в этом потоке:</li> </ul><p><img src="https://i.stack.imgur.com/6mtb3.png" alt="enter image description here"></p><ul> <li>если для потока пользовательского интерфейса нет работы, то существуют незанятые промежутки, которые могут использоваться вычислениями, не относящимися к пользовательскому интерфейсу.</li> <li>чтобы использовать указанные пробелы, используйте <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke.aspx">system.windows.forms.control.invoke</a> или <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke.aspx">system.windows.forms.control.begininvoke</a> методы:</li> </ul><p><img src="https://i.stack.imgur.com/wqpoj.png" alt="enter image description here"></p><h2>методы begininvoke и invoke</h2><ul> <li>затраты на обработку вызываемого метода должны быть небольшими, а также затраты на обработку методов-обработчиков событий, потому что там используется поток пользовательского интерфейса - тот же, что отвечает за обработку пользовательского ввода. независимо от того, --- это <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke.aspx">system.windows.forms.control.invoke</a> или <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke.aspx">system.windows.forms.control.begininvoke</a> .</li> <li>для выполнения вычислительных дорогостоящих операций всегда используйте отдельный поток. поскольку .net 2.0 <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx">backgroundworker</a> предназначена для выполнения дорогостоящих операций в windows forms. однако в новых решениях вы должны использовать шаблон async-await, как описано <a href="https://stackoverflow.com/a/18033198/2042090">здесь</a> .</li> <li>используйте <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke.aspx">system.windows.forms.control.invoke</a> или <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke.aspx">system.windows.forms.control.begininvoke</a> только для обновления пользовательского интерфейса. если вы используете их для тяжелых вычислений, ваше приложение заблокирует:</li> </ul><p><img src="https://i.stack.imgur.com/ufbqr.png" alt="enter image description here"></p><h3>взывать</h3><ul> <li> <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke.aspx">system.windows.forms.control.invoke</a> заставляет отдельный поток ожидать завершения вызванного метода:</li> </ul><p><img src="https://i.stack.imgur.com/xmfju.png" alt="enter image description here"></p><h3>begininvoke</h3><ul> <li> <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke.aspx">system.windows.forms.control.begininvoke</a> не заставляет отдельный поток ждать завершения вызванного метода:</li> </ul><p><img src="https://i.stack.imgur.com/8k5kn.png" alt="enter image description here"></p><h2>кодовое решение</h2><p>прочитать ответы на вопрос <a href="https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c">как обновить графический интерфейс из другого потока в c #?</a> . для c # 5.0 и .net 4.5 рекомендуемое решение <a href="https://stackoverflow.com/a/18033198/2042090">здесь</a> .</p>

<p>вы хотите использовать invoke или begininvoke только для минимальной части работы, необходимой для изменения пользовательского интерфейса. ваш "тяжелый" метод должен выполняться в другом потоке (например, через backgroundworker), но затем использовать control.invoke/control.begininvoke просто для обновления пользовательского интерфейса. таким образом, ваш поток пользовательского интерфейса будет свободен для обработки событий пользовательского интерфейса и т.д.</p><p>см. мой <a href="http://pobox.com/~skeet/csharp/threads">многопотоковая статья</a> для <a href="http://pobox.com/~skeet/csharp/threads/winforms.shtml">пример winforms</a> - хотя статья была написана до появления backgroundworker, и, боюсь, я не обновил ее в этом отношении. backgroundworker просто немного упрощает обратный вызов.</p>

<p>у меня была эта проблема с <code>filesystemwatcher</code> и я обнаружил, что следующий код решил эту проблему:</p><p><code>fsw.synchronizingobject = this</code></p><p>затем элемент управления использует текущий объект формы для обработки событий и, следовательно, будет в том же потоке.</p>

<p>я знаю, что уже слишком поздно. однако даже сегодня, если у вас возникли проблемы с доступом к многопоточному контролю? это самый короткий ответ до даты: p</p><pre><code>invoke(new action(() => { label1.text = "woohoo!!!"; })); </code></pre><p>вот так я получаю доступ к любому элементу управления формы из потока.</p>

<p>я считаю, что код проверки и вызова, который необходимо замусорить во всех методах, связанных с формами, слишком многословен и не нужен. вот простой метод расширения, который позволяет полностью избавиться от него:</p><pre><code>public static class extensions { public static void invoke<tcontroltype>(this tcontroltype control, action<tcontroltype> del) where tcontroltype : control { if (control.invokerequired) control.invoke(new action(() => del(control))); else del(control); } } </code></pre><p>и тогда вы можете просто сделать это:</p><pre><code>textbox1.invoke(t => t.text = "a"); </code></pre><p>нет больше возиться - просто.</p>

<p>элементы управления в .net не являются потокобезопасными. это означает, что вы не должны обращаться к элементу управления из потока, отличного от того, в котором он находится. чтобы обойти это, вам нужно <em>вызвать</em> элемент управления, который пытается выполнить ваш второй пример.</p><p>тем не менее, в вашем случае все, что вы сделали, это передали долгосрочный метод обратно в основной поток. конечно, это не совсем то, что вы хотите сделать. вам нужно немного переосмыслить, чтобы все, что вы делаете в главном потоке, устанавливало быстрое свойство здесь и там.</p>

<p>самое чистое (и правильное) решение для проблем многопоточности пользовательского интерфейса - использовать synchronizationcontext, см. статью <a href="http://www.lostechies.com/blogs/gabrielschenker/archive/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application.aspx">синхронизация вызовов пользовательского интерфейса в многопоточном приложении</a> , это очень хорошо объясняется.</p>

<p>новый взгляд с использованием async/await и обратных вызовов. вам нужна только одна строка кода, если вы сохраняете метод расширения в своем проекте.</p><pre><code>/// <summary> /// a new way to use tasks for asynchronous calls /// </summary> public class example { /// <summary> /// no more delegates, background workers etc. just one line of code as shown below /// note it is dependent on the xtask class shown next. /// </summary> public async void examplemethod() { //still on gui/original thread here //do your updates before the next line of code await xtask.runasync(() => { //running an asynchronous task here //cannot update gui thread here, but can do lots of work }); //can update gui/original thread on this line } } /// <summary> /// a class containing extension methods for the task class /// put this file in folder named extensions /// use prefix of x for the class it extends /// </summary> public static class xtask { /// <summary> /// runasync is an extension method that encapsulates the task.run using a callback /// </summary> /// <param name="code">the caller is called back on the new task (on a different thread)</param> /// <returns></returns> public async static task runasync(action code) { await task.run(() => { code(); }); return; } } </code></pre><p>вы можете добавить другие вещи в метод extension, например, обернуть его в оператор try/catch, позволяя вызывающей стороне сообщить ему, какой тип возвращать после завершения, обратный вызов исключения для вызывающей стороны:</p><p><strong>добавление try catch, автоматическое ведение журнала исключений и callback</strong></p><pre><code> /// <summary> /// run async /// </summary> /// <typeparam name="t">the type to return</typeparam> /// <param name="code">the callback to the code</param> /// <param name="error">the handled and logged exception if one occurs</param> /// <returns>the type expected as a competed task</returns> public async static task<t> runasync<t>(func<string,t> code, action<exception> error) { var done = await task<t>.run(() => { t result = default(t); try { result = code("code here"); } catch (exception ex) { console.writeline("unhandled exception: " + ex.message); console.writeline(ex.stacktrace); error(ex); } return result; }); return done; } public async void howtouse() { //we now inject the type we want the async routine to return! var result = await runasync<bool>((code) => { //write code here, all exceptions are logged via the wrapped try catch. //return what is needed return someboolvalue; }, error => { //exceptions are already handled but are sent back here for further processing }); if (result) { //we can now process the result because the code above awaited for the completion before //moving to this statement } } </code></pre>

<p>вам нужно взглянуть на пример backgroundworker:<br> <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx">http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx</a> особенно о том, как он взаимодействует со слоем пользовательского интерфейса. судя по вашей публикации, это, кажется, отвечает вашим проблемам.</p>

<p>следуйте простейшему (на мой взгляд) способу изменения объектов из другого потока:</p><pre><code>using system.threading.tasks; using system.threading; namespace teste { public partial class form1 : form { public form1() { initializecomponent(); } private void button1\_click(object sender, eventargs e) { action<string> delegateteste\_modifytext = thread\_mod; invoke(delegateteste\_modifytext, "modify by thread"); } private void thread\_mod(string teste) { textbox1.text = teste; } } } </code></pre>

<p>я нашел в этом необходимость, когда программировал контроллер приложения ios-phone monotouch в визуальной студии. проект winforms прототип за пределами xamarin stuidio. предпочитая программировать в vs поверх студии xamarin, я хотел, чтобы контроллер был полностью отделен от каркаса телефона. таким образом, реализация этого для других сред, таких как android и windows phone, будет намного проще для использования в будущем.</p><p>я хотел найти решение, в котором графический интерфейс мог бы реагировать на события без бремени обработки кода переключения между потоками за каждым нажатием кнопки. по сути, пусть контроллер класса справится с этим, чтобы клиентский код был простым. вы могли бы иметь много событий в gui, где, как если бы вы могли обрабатывать это в одном месте в классе, было бы чище. я не многопрофильный эксперт, дайте мне знать, если это неправильно.</p><pre><code>public partial class form1 : form { private examplecontroller.mycontroller controller; public form1() { initializecomponent(); controller = new examplecontroller.mycontroller((isynchronizeinvoke) this); controller.finished += controller\_finished; } void controller\_finished(string returnvalue) { label1.text = returnvalue; } private void button1\_click(object sender, eventargs e) { controller.submittask("do it"); } } </code></pre><p>форма gui не знает, что контроллер выполняет асинхронные задачи.</p><pre><code>public delegate void finishedtaskshandler(string returnvalue); public class mycontroller { private isynchronizeinvoke \_syn; public mycontroller(isynchronizeinvoke syn) { \_syn = syn; } public event finishedtaskshandler finished; public void submittask(string somevalue) { system.threading.threadpool.queueuserworkitem(state => submittask(somevalue)); } private void submittask(string somevalue) { somevalue = somevalue + " " + datetime.now.tostring(); system.threading.thread.sleep(5000); //finished(somevalue); this causes cross threading error if called like this. if (finished != null) { if (\_syn.invokerequired) { \_syn.invoke(finished, new object[] { somevalue }); } else { finished(somevalue); } } } } </code></pre>

<p>это не рекомендуемый способ устранения этой ошибки, но вы можете быстро ее устранить, это сделает работу. я предпочитаю это для прототипов или демонстраций. добавлять</p><pre><code>checkforillegalcrossthreadcalls = false </code></pre><p>в конструкторе <code>form1()</code>.</p>

<p>вот альтернативный способ, если объект, с которым вы работаете, не имеет</p><pre><code>(invokerequired) </code></pre><p>это полезно, если вы работаете с основной формой в классе, отличном от основной формы, с объектом, который находится в главной форме, но не имеет invokerequired</p><pre><code>delegate void updatemainformobject(formobjecttype objectwithoutinvoke, string text); private void updateformobjecttype(formobjecttype objectwithoutinvoke, string text) { mainform.invoke(new updatemainformobject(updateobject), objectwithoutinvoke, text); } public void updateobject(toolstripstatuslabel objectwithoutinvoke, string text) { objectwithoutinvoke.text = text; } </code></pre><p>он работает так же, как и выше, но это другой подход, если у вас нет объекта с invokerequired, но есть доступ к mainform</p>

<p>в том же ключе, что и в предыдущих ответах, но очень короткое дополнение, позволяющее использовать все свойства элемента управления без исключения межпотокового вызова.</p><p><strong>вспомогательный метод</strong></p><pre><code>/// <summary> /// helper method to determin if invoke required, if so will rerun method on correct thread. /// if not do nothing. /// </summary> /// <param name="c">control that might require invoking</param> /// <param name="a">action to preform on control thread if so.</param> /// <returns>true if invoke required</returns> public bool controlinvokerequired(control c, action a) { if (c.invokerequired) c.invoke(new methodinvoker(delegate { a(); })); else return false; return true; } </code></pre><p><strong>пример использования</strong></p><pre><code>// usage on textbox public void updatetextbox1(string text) { //check if invoke requied if so return - as i will be recalled in correct thread if (controlinvokerequired(textbox1, () => updatetextbox1(text))) return; textbox1.text = ellapsed; } //or any control public void updatecontrol(color c, string s) { //check if invoke requied if so return - as i will be recalled in correct thread if (controlinvokerequired(mycontrol, () => updatecontrol(c, s))) return; mycontrol.text = s; mycontrol.backcolor = c; } </code></pre>

<pre><code>this.invoke(new methodinvoker(delegate { //your code here; })); </code></pre>

<p>например, чтобы получить текст из элемента управления потока пользовательского интерфейса:</p><pre class="lang-vb prettyprint-override"><code>private delegate function getcontroltextinvoker(byval ctl as control) as string private function getcontroltext(byval ctl as control) as string dim text as string if ctl.invokerequired then text = cstr(ctl.invoke( new getcontroltextinvoker(addressof getcontroltext), ctl)) else text = ctl.text end if return text end function </code></pre>

<p> <a href="https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c">тот же вопрос: как обновлять графический интерфейс пользователя из другого потока</a> </p><p>два пути:</p><ol> <li> <p>верните значение в e.result и используйте его для установки значения текстового поля в событии backgroundworker\_runworkercompleted</p> </li> <li> <p>объявите некоторую переменную для хранения таких значений в отдельном классе (который будет работать как держатель данных). создайте статический экземпляр этого класса, и вы можете получить к нему доступ через любой поток.</p> </li> </ol><p>пример:</p><pre><code>public class data\_holder\_for\_controls { //it will hold value for your label public string status = string.empty; } class demo { public static data\_holder\_for\_controls d1 = new data\_holder\_for\_controls(); static void main(string[] args) { threadstart ts = new threadstart(perform\_logic); thread t1 = new thread(ts); t1.start(); t1.join(); //your\_label.text=d1.status; --- can access it from any thread } public static void perform\_logic() { //put some code here in this function for (int i = 0; i < 10; i++) { //statements here } //set result in status variable d1.status = "task done"; } } </code></pre>

<p>действие у; // объявлено внутри класса</p><p>label1.invoke (у = () => label1.text = "текст");</p>

<p>просто используйте это:</p><pre><code>this.invoke((methodinvoker)delegate { yourcontrol.property= value; // runs thread safe }); </code></pre>