C# Операция кросс-потока недопустима в BackgroundWorker - PullRequest
0 голосов
/ 24 апреля 2020

Я добавляю элементы в список при загрузке основной формы:

private void MainForm_Load(object sender, EventArgs e)
{
    Dictionary<string, string> item = new Dictionary<string, string>();
    item.Add("Test 1", "test1");
    item.Add("Test 2", "test 2");

    cmbTest.DataSource = new BindingSource(item, null);
    cmbTest.DisplayMember = "Key";
    cmbTest.ValueMember = "Value";
}

Затем я пытаюсь получить значение выбранного элемента в BackgroundWorker, но это не удается.

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
    MessageBox.Show(test);
}

Ответы [ 4 ]

2 голосов
/ 24 апреля 2020

Фоновый работник не должен пытаться что-либо делать с пользовательским интерфейсом. Единственное, что может сделать поток, это уведомить тех, кто интересуется, что фоновый работник рассчитал что-то примечательное.

Это уведомление сделано с помощью Event ProgressChanged.

  • Использование Visual Studio Designer создать BackGroundWorker.
  • Позволить конструктору добавить обработчики событий для DoWork, ProgressChanged и, если необходимо, RunWorkerCompleted
  • Если BackGroundWorker хочет уведомить форму о том, что что-то должно отображаться, используйте ProgressChanged

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

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

Во время расчета BackGroundWorker регулярно уведомляет форму о ходе и промежуточных рассчитанных значениях. Когда он заканчивается, возвращается конечный результат.

Код будет выглядеть следующим образом:

private void InitializeComponent()
{
    this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
    this.SuspendLayout();
    this.backgroundWorker1.DoWork += new DoWorkEventHandler(this.DoBackgroundWork);
    this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(this.NotifyProgress);
    this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
                                                 this.OnBackgounrWorkCompleted);
    ...
}

Когда выбран элемент comboBox1, фоновый работник запускается с использованием выбранного значения. Пока фоновый работник запущен, пользователи не могут снова изменить combobox1, потому что мы не можем запустить тот же самый backgroundworker, пока он все еще занят.

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

Обратите внимание, что остальная часть формы все еще работает, пока фоновый работник вычисляет.

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    ComboBox comboBox = (ComboBox)sender;

    // disable ComboBox, show ProgressBar:
    comboBox.Enabled = false;
    this.progressBar1.Minimum = 0;
    this.progressBar1.Maximum = 100;
    this.progressBar1.Value = 0;
    this.progressBar1.Visible = true;

    // start the backgroundworker using the selected value:
    this.backgroundWorker1.RunWorkerAsync(comboBox.SelectedValue);
}

Фоновая работа:

private void DoBackgroundWork(object sender, DoWorkEventArgs e)
{
    // e.Argument contains the selected value of the combobox
    string test = ((KeyValuePair<string, string>)e.Argument;

    // let's do some lengthy processing:
    for (int i=0; i<10; ++i)
    {
        string intermediateText = Calculate(test, i);

        // notify about progress: use a percentage and intermediateText
        this.backgroundWorker1.ReportProgress(10*i, intermediateText);
    }

    string finalText = Calculate(test, 10);

    // the result of the background work is finalText
    e.Result = finalText;
}

Регулярно ваша форма получает уведомление о прогрессе: пусть она обновляет ProgressBar и показывает промежуточный текст в Label1

private void NotifyProgress(object sender, ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
    this.label1.Text = e.UserState.ToString();
}

Когда BackgroundWorker завершается, окончательный текст отображается в label1, индикатор выполнения исчезает, а комбинированный список снова включается:

private void OnBackgoundWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.label1.Text = e.Result.ToString();
    this.progressBar1.Visible = false;
    this.comboBox1.Enabled = true;
}
0 голосов
/ 24 апреля 2020

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

 this.Invoke(new Action(() =>
 {
   string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
 }));

Если вам нужны значения для асинхронной c обработки

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
 string test;
 this.Invoke(new Action(() =>
 {
     //Any other things you need from UI thread
     test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
 }));
 //Here you have access to UI thread values
}
0 голосов
/ 24 апреля 2020

Другое решение.

Объявление поля формы:

private string _value;

Введите значение в этом поле из элемента управления пользовательского интерфейса в том месте, где вы запускаете BackgroundWorker

private void RunWorkButton_Click(object sender, EventArgs e)
{
    _value = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
    testWorker.RunWorkerAsync();
}

Далее используйте это поле в методе DoWork:

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // use _value somehow
    string test = _value;
}
0 голосов
/ 24 апреля 2020

Элементы UI доступны только для потока пользовательского интерфейса.

Если вы работаете с WinForms, вы можете использовать Control.InvokeRequired flag и Control.Invoke метод:

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    if(!cmbTest.InvokeRequired)
    {
        string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
        MessageBox.Show(test);
    }
    else 
    {
        string test;
        Invoke(() => test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value);
        Invoke(() => MessageBox.Show(test));
    }
}

Control.InvokeRequired получает значение, указывающее, должен ли вызывающий объект вызывать метод invoke при вызове метода для элемента управления, поскольку вызывающий объект находится в потоке, отличном от того, в котором создан элемент управления. .

Control.Invoke выполняет указанный делегат в потоке, которому принадлежит основной дескриптор окна элемента управления.

Если вы работаете с WPF, то для него тоже есть решение. Dispatcher.Invoke синхронно выполняет указанный делегат в потоке, с которым связан Диспетчер:

private void TestWorker_DoWork(object sender, DoWorkEventArgs e)
{
    this.Dispatcher.Invoke(() => {
        string test = ((KeyValuePair<string, string>)cmbTest.SelectedItem).Value;
        MessageBox.Show(test);
    });
}

Подробнее о потоко-безопасных вызовах для windows элементов управления форм можно прочитать из Модель потоков WPF в документах Microsoft.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...