#c# #winforms #multithreading
#c# #winforms #многопоточность
Вопрос:
У меня есть проект, закодированный в .NET Winforms. Мне нужно реализовать операцию интеллектуального анализа данных, распечатать текст в текстовое поле и обновить ход выполнения.
Я пытался использовать BackgroundWorker для выполнения, но он выдает исключение InvalidOperationException (недопустимая операция между потоками: управление ‘xxxxx’, доступ к которому осуществляется из потока, отличного от потока, в котором он был создан)
Чтобы сузить возможные причины проблемы, я запустил новый проект, включающий следующее: Кнопка — для запуска ярлыка BackgroundWorker — для печати текста. И индикатор выполнения.
Однако результат тот же. Я искал в SOF, и мне сказали использовать делегат, но я с ним не знаком.
Это пример кода, который выдает ошибку:
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace TestProject
{
public partial class Form1 : Form
{
private readonly BackgroundWorker _bw = new BackgroundWorker();
public Form1()
{
InitializeComponent();
_bw.DoWork = RosterWork;
_bw.ProgressChanged = BwProgressChanged;
_bw.RunWorkerCompleted = BwRunWorkerCompleted;
_bw.WorkerReportsProgress = true;
_bw.WorkerSupportsCancellation = false;
}
private void RosterWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
for (int i = 0; i < 1000; i )
{
label1.Text = i.ToString();
_bw.ReportProgress(Convert.ToInt32((i * (100 / 1000))));
}
}
private void BwProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void btnStart_Click(object sender, EventArgs e)
{
progressBar1.Show();
_bw.RunWorkerAsync();
}
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Hide();
}
}
}
Обновление:
Я следую ответу Джона Скита, он действительно работает в моем тестовом проекте, но вернемся к моему реальному проекту,
Макет моей формы:
Form — TabControl — Tab1 -Tab1Panel -TextBox1
При достижении этой строки :
TextBox txtbox1 = new TextBox();
Tab1Panel.Controls.Add(txtbox1);
Ошибка по-прежнему возникает, когда я программно добавляю текстовое поле в панель управления.
Наконец, я заменяю на это:
if (Tab1Panel.InvokeRequired)
Tab1Panel.Invoke((MethodInvoker)delegate { Tab1Panel.Controls.Add(txtbox1); });
else
Tab1Panel.Controls.Add(txtbox1);
Все работает. Как определить, требуется ли элемент управления InvokeRequired, указан ли он в качестве элемента управления?
Ответ №1:
Это проблема:
label1.Text = i.ToString();
Вы пытаетесь изменить текст метки внутри BackgroundWorker
, который не выполняется в потоке пользовательского интерфейса. Смысл BackgroundWorker
в том, чтобы выполнять всю работу, не связанную с пользовательским интерфейсом, используя ReportProgress
для периодического «возврата» к потоку пользовательского интерфейса и обновления пользовательского интерфейса в соответствии с достигнутым прогрессом.
Так что либо вам label1.Text
также нужно изменить BwProgressChanged
, либо вам нужно использовать Control.Invoke
/ BeginInvoke
так же, как и в любом другом фоновом потоке:
// Don't capture a loop variable in a lambda expression...
int copy = i;
Action updateLabel = () => label1.Text = copy.ToString();
label1.BeginInvoke(updateLabel);
Подробнее о части копирования см. Сообщение в блоге Эрика Липперта «Закрытие переменной цикла считается вредным». В данном конкретном случае это проблема только потому, что я использую BeginInvoke
. Это можно изменить на просто:
Action updateLabel = () => label1.Text = i.ToString();
label1.Invoke(updateLabel);
… но теперь фоновый рабочий всегда будет ждать, пока пользовательский интерфейс догонит, прежде чем он продолжит работу, что в реальной жизни обычно не то, что вы хотите. Я обычно предпочитаю BeginInvoke
over Invoke
.
Комментарии:
1. Спасибо, Джон Скит! Я ищу в Google about . СЕТЕВАЯ потоковая обработка. Я прочитал какую-то статью о потоке / ThreadStart / ThreadPool / Delegate . Кажется, что все они больше похожи на ожидаемые стандарты программирования потоков. Так правильно ли использовать BackgroundWorker?
2. @Shiba: Боюсь, я не совсем понимаю ваш комментарий, особенно «все они больше похожи на ожидаемые стандарты»…
Ответ №2:
используйте этот код, который будет работать
BeginInvoke((MethodInvoker)delegate
{
TextBox1.Text = "your text here";
});
Ответ №3:
Вы получаете доступ к своей метке, которая была создана в потоке GUI, из вашего потока backgroundworker. Доступ к элементу управления Windows из потока, отличного от того, в котором был создан элемент управления, запрещен; поэтому вам выдается исключение.
Вы не должны обращаться к метке напрямую, но вместо этого вы должны вызвать событие из своего потока и убедиться, что оно вызвано в правильном потоке, который сигнализирует пользовательскому интерфейсу, чтобы вы могли изменить содержимое метки.
Ответ №4:
Вы должны выполнить методы элемента управления из потока, который их создал. Чтобы обновить их из другого потока, используйте Invoke . Пример того, как это можно сделать, можно найти здесь . Вы также должны прочитать многопоточность с формами и элементами управления в MSDN.