У меня есть сценарий. (Windows Forms, C#, .NET)
UserControl_Load, пользовательский интерфейс перестает отвечать на запросы на время выполнения метода загрузки.Псевдокод будет выглядеть так:
КОД 1
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.
}
}
Исключение, которое он дал, было
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.
Чтобы узнать больше об этом, я немного погуглил, и появилось предложение, например, использовать следующий код
КОД 2
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
}
}
НО НО ... кажется, я вернулся на круги своя. Приложение снова перестать отвечать. Похоже, это связано с выполнением строки # 1 if condition. Задача загрузки снова выполняется родительским потоком, а не третьим, созданным мной.
Не знаю, правильно я это понял или неправильно. Я новичок в потоках.
Как мне решить эту проблему, а также каков эффект выполнения блока Line # 1 if?
Ситуация такая: я хочу загрузить данные в глобальную переменную на основе значения элемента управления. Я не хочу изменять значение элемента управления дочернего потока. Я не собираюсь делать это из дочернего потока.
Таким образом, доступ только к значению, чтобы соответствующие данные могли быть извлечены из базы данных.





Элементы управления в .NET обычно не являются потокобезопасными. Это означает, что вам не следует обращаться к элементу управления из потока, отличного от того, в котором он находится. Чтобы обойти это, вам нужно вызывать элемента управления, что и пытается сделать ваш второй образец.
Однако в вашем случае все, что вы сделали, - это передали длительный метод обратно в основной поток. Конечно, это не совсем то, что вы хотите делать. Вам нужно немного переосмыслить это, чтобы все, что вы делаете в основном потоке, - это установка быстрого свойства здесь и там.
Вы хотите использовать Invoke или BeginInvoke только для минимума работы, необходимой для изменения пользовательского интерфейса. Ваш «тяжелый» метод должен выполняться в другом потоке (например, через BackgroundWorker), но затем использовать Control.Invoke / Control.BeginInvoke только для обновления пользовательского интерфейса. Таким образом, ваш поток пользовательского интерфейса будет свободен для обработки событий пользовательского интерфейса и т. д.
См. Мой разделить статью для Пример WinForms - хотя статья была написана до того, как на сцене появился BackgroundWorker, и, боюсь, я не обновлял ее в этом отношении. BackgroundWorker просто немного упрощает обратный вызов.
вот в этом моем состоянии. я даже не меняю UI. Я просто получаю доступ к его текущим значениям из дочернего потока. любые предложения по реализации
Вам по-прежнему необходимо перейти к потоку пользовательского интерфейса даже просто для доступа к свойствам. Если ваш метод не может продолжаться до тех пор, пока значение не будет получено, вы можете использовать делегат, который возвращает значение. Но да, пройдите через поток пользовательского интерфейса.
Привет, Джон, я считаю, что ты ведешь меня в правильном направлении. Да, мне нужно значение без него, я не могу продолжить. Не могли бы вы подробнее рассказать о том, что «Использование делегата, возвращающего значение». Спасибо
Используйте делегат, например Func <string>: string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (Предполагая, что вы используете C# 3.0 - в противном случае вы могли бы использовать анонимный метод.)
Согласно Комментарий к обновлению Прерака К. (с момента удаления):
I guess I have not presented the question properly.
Situation is this: I want to load data into a global variable based on the value of a control. I don't want to change the value of a control from the child thread. I'm not going to do it ever from a child thread.
So only accessing the value so that corresponding data can be fetched from the database.
Тогда желаемое решение должно выглядеть так:
UserContrl1_LOadDataMethod()
{
string name = "";
if (textbox1.InvokeRequired)
{
textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
}
if (name == "MyName")
{
// do whatever
}
}
Выполните серьезную обработку в отдельном потоке до, вы пытаетесь переключиться обратно в поток управления. Например:
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;
}
}
}
Я давно не занимался программированием на C#, но, судя по статье MSDN и моим разрозненным знаниям, похоже, что это так.
Разница в том, что BeginInvoke () работает асинхронно, а Invoke () - синхронно. stackoverflow.com/questions/229554/…
Вам нужно посмотреть на пример Backgroundworker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Особенно то, как он взаимодействует со слоем пользовательского интерфейса. Судя по вашему сообщению, это, кажется, ответ на ваши вопросы.
У меня была эта проблема с FileSystemWatcher, и я обнаружил, что следующий код решил проблему:
fsw.SynchronizingObject = this
Затем элемент управления использует текущий объект формы для обработки событий и, следовательно, будет находиться в том же потоке.
Это спасло мой бекон. В VB.NET я использовал .SynchronizingObject = Me
Самым чистым (и правильным) решением проблем с перекрестным потоком пользовательского интерфейса является использование SynchronizationContext, см. Статью Синхронизация вызовов пользовательского интерфейса в многопоточном приложении, в ней это очень хорошо объясняется.
Вот альтернативный способ, если у объекта, с которым вы работаете, нет
(InvokeRequired)
Это полезно, если вы работаете с основной формой в классе, отличном от основной формы, с объектом, который находится в основной форме, но не имеет InvokeRequired.
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;
}
Он работает так же, как и выше, но это другой подход, если у вас нет объекта с invokerequired, но у вас есть доступ к MainForm
Я обнаружил необходимость в этом при программировании контроллера монотач-приложения для iOS-Phone в проекте прототипа Visual Studio winforms за пределами xamarin stuidio. Предпочитая программировать в VS, а не в xamarin studio, насколько это возможно, я хотел, чтобы контроллер был полностью отделен от инфраструктуры телефона. Таким образом, реализация этого для других фреймворков, таких как Android и Windows Phone, будет намного проще для использования в будущем.
Мне нужно было решение, в котором графический интерфейс мог бы реагировать на события без необходимости иметь дело с кодом переключения между потоками за каждым нажатием кнопки. По сути, позвольте контроллеру класса обрабатывать это, чтобы клиентский код оставался простым. У вас может быть много событий в графическом интерфейсе, где, как если бы вы могли обрабатывать их в одном месте в классе, было бы чище. Я не многопрофильный эксперт, дайте мне знать, если это что-то не так.
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");
}
}
Форма графического интерфейса пользователя не знает, что контроллер выполняет асинхронные задачи.
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);
}
}
}
}
Пожалуйста, прочтите Модель потоковой передачи в приложениях пользовательского интерфейса (старая ссылка на VB здесь), чтобы понять основные концепции. Ссылка ведет на страницу, описывающую потоковую модель WPF. Однако в Windows Forms используется та же идея.





Прочтите ответы на вопрос Как обновить графический интерфейс из другого потока на C#?. Для C# 5.0 и .NET 4.5 рекомендуемое решение - здесь.
Например, чтобы получить текст из элемента управления потоком пользовательского интерфейса:
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
В том же духе, что и в предыдущих ответах, но очень короткое дополнение, которое позволяет использовать все свойства элемента управления без исключения вызова между потоками.
Вспомогательный метод
/// <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;
}
Пример использования
// 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;
}
Я считаю, что код проверки и вызова, который должен быть замусорен во всех методах, связанных с формами, слишком подробен и ненужен. Вот простой метод расширения, который позволяет полностью избавиться от него:
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);
}
}
И тогда вы можете просто сделать это:
textbox1.Invoke(t => t.Text = "A");
Больше не нужно возиться - просто.
что здесь
@Rawat t в этом случае будет textbox1 - он передается в качестве аргумента
Следуйте простейшему (на мой взгляд) способу модификации объектов из другого потока:
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;
}
}
}
Новый вид с использованием Async / Await и обратных вызовов. Вам понадобится всего одна строка кода, если вы сохраните метод расширения в своем проекте.
/// <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;
}
}
Вы можете добавить другие вещи к методу Extension, например, заключить его в оператор Try / Catch, позволяя вызывающей стороне сообщить ему, какой тип возвращать после завершения, обратный вызов исключения для вызывающей стороны:
Добавление Try Catch, Auto Exception Logging и CallBack
/// <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
}
}
Тот же вопрос: как-обновить-графический-интерфейс-из-другого-потока-в-c
Два пути:
Возвращаемое значение в e.result и его использование для установки значения текстового поля yout в событии backgroundWorker_RunWorkerCompleted.
Объявите некоторую переменную для хранения таких значений в отдельном классе (который будет работать как держатель данных). Создайте статический экземпляр этого класса, и вы можете получить к нему доступ через любой поток.
Пример:
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";
}
}
Это не рекомендуемый способ решения этой ошибки, но вы можете быстро ее подавить, и он выполнит свою работу. Я предпочитаю это для прототипов или демонстраций. добавлять
CheckForIllegalCrossThreadCalls = false
в конструкторе Form1().
Я знаю, что уже слишком поздно. Однако даже сегодня, если у вас возникают проблемы с доступом к элементам управления кросс-потоком? Это самый короткий ответ на сегодняшний день: P
Invoke(new Action(() =>
{
label1.Text = "WooHoo!!!";
}));
Вот как я получаю доступ к любому элементу управления формой из потока.
Это дает мне Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Решил здесь
this.Invoke(new MethodInvoker(delegate
{
//your code here;
}));
Действие y; // объявлен внутри класса
label1.Invoke (y = () => label1.Text = "текст");
Есть два варианта операций с поперечной резьбой.
Control.InvokeRequired Property
а второй - использовать
SynchronizationContext Post Method
Control.InvokeRequired полезен только при работе элементов управления, унаследованных от класса Control, в то время как SynchronizationContext можно использовать где угодно. Некоторая полезная информация представлена по следующим ссылкам
Пользовательский интерфейс обновления перекрестных потоков | .Сеть
Пользовательский интерфейс межпоточного обновления с использованием SynchronizationContext | .Сеть
Просто используйте это:
this.Invoke((MethodInvoker)delegate
{
YourControl.Property= value; // runs thread safe
});
Простой и многоразовый способ решения этой проблемы.
Метод расширения
public static class FormExts
{
public static void LoadOnUI(this Form frm, Action action)
{
if (frm.InvokeRequired) frm.Invoke(action);
else action.Invoke();
}
}
Пример использования
private void OnAnyEvent(object sender, EventArgs args)
{
this.LoadOnUI(() =>
{
label1.Text = "";
button1.Text = "";
});
}
Кто-нибудь может возразить против такого подхода? Это кажется безумно простым по сравнению с популярными ответами.
Для моего конкретного случая этой ошибки я обнаружил, что обходной путь заключается в использовании BackgroundWorker в форме для обработки частей кода, требующих большого объема данных. (т.е. поместите весь проблемный код в метод backgroundWorker1_DoWork () и вызовите его через backgroundWorker1.RunWorkerAsync ()) ... Эти два источника указали мне правильное направление: stackoverflow.com/questions/4806742/…youtube.com/watch?v=MLrrbG6V1zM