У меня есть приложение, в котором два потока асинхронно записывают в одно текстовое поле. Это работает, за исключением того, что второй поток для записи в текстовое поле перезаписывает строку, которую только что написал первый поток. Будем очень признательны за любые мысли или понимание проблемы. Я использую Microsoft Visual C # 2008 Express Edition. Спасибо.

  delegate void SetTextCallback(string text);

  private void SetText(string text)
  {
     this.textBox1.Text += text;
     this.textBox1.Select(textBox1.Text.Length, 0);
     this.textBox1.ScrollToCaret();
  }

  private void backgroundWorkerRx_DoWork(object sender, DoWorkEventArgs e)
  {
     string sText = "";

     // Does some receive work and builds sText

     if (textBox1.InvokeRequired)
     {
        SetTextCallback d = new SetTextCallback(SetText);
        this.Invoke(d, new object[] { sText });
     }
     else
     {
        SetText(sText);
     }
  }
2
Jim Fell 11 Янв 2010 в 19:59
Что именно вы хотите, чтобы мы сделали? это ожидаемое поведение, когда 2 потока пишут одно и то же ... вы хотите вместо этого добавить текст ??
 – 
Stan R.
11 Янв 2010 в 20:04
2
Функция SetText добавляет текст.
 – 
SLaks
11 Янв 2010 в 20:06
1
Похоже, ваш код будет работать так, как написано. Возможно, вы что-то отредактировали при публикации этого сообщения, что вызывает ошибку?
 – 
Jon B
11 Янв 2010 в 20:10

3 ответа

Лучший ответ

РЕДАКТИРОВАТЬ : это может не решить проблему, но вы можете обработать событие ProgressChanged объекта BackgroundWorkers и установить там текст.

Например:

void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
    SetText((string)e.UserState);
}  //Make this method handle the RunWorkerCompleted for both workers

//In DoWork:
    worker.ReportProgress(0, sText);

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

Кстати, вам, вероятно, следует переименовать SetText в AppendText, чтобы код был понятнее.
Кроме того, вы можете использовать встроенный делегат Action<String> вместо создания собственного типа делегата SetTextCallback.

РЕДАКТИРОВАТЬ : вам, вероятно, следует переместить проверку InvokeRequired на SetText.

Например:

private void AppendText(string text) {
    if(textBox1.InvokeRequired) {
        textBox1.Invoke(new Action<string>(AppendText), text);
        return;
    }
    this.textBox1.AppendText(text);
    this.textBox1.SelectionStart = textBox1.TextLength;
    this.textBox1.ScrollToCaret();
}
3
SLaks 11 Янв 2010 в 20:47
Спасибо за предложение, но мой фоновый обработчик приема никогда не завершает работу. Он настроен на постоянное выполнение, потому что ему необходимо получать сообщения, которые непрерывно отправляются с интервалами 10 мс.
 – 
Jim Fell
11 Янв 2010 в 20:35
Тогда вы можете позвонить ReportProgress.
 – 
SLaks
11 Янв 2010 в 20:41
1
Это правильный способ сделать это. На мой взгляд, простое изменение тела кода SetText (или AppendText, как его назвал здесь SLaks), чтобы проверка InvokeRequired находилась внутри, должно помочь. Использование ReportProgress - самое удобное решение, поскольку оно не требует каких-либо махинаций InvokeRequired. Затем, направляя операцию через поток пользовательского интерфейса, в текстовое поле будет происходить только одна запись за раз.
 – 
Andras Zoltan
11 Янв 2010 в 20:57
@Zoltan: Он уже вызывает это в потоке пользовательского интерфейса, используя Invoke. Все, что я сделал во второй части, это переместил вызов Invoke.
 – 
SLaks
11 Янв 2010 в 21:05
SLaks, похоже, это действительно хорошо работает в том смысле, что Textbox1 теперь обновляется очень быстро, что является одной из тех вещей, для которых я снимал. Однако, похоже, это упреждает контроль над другими компонентами в моем приложении, как только backgroundWorkerRx_DoWork начинает обрабатывать данные. Может ли это быть из-за огромного количества данных, добавляемых в текстовое поле?
 – 
Jim Fell
13 Янв 2010 в 18:54

Попробуйте заблокировать раздел кода, с которым, по вашему мнению, возникают проблемы с параллелизмом.

lock(someObjectUsedOnlyForLocking)
{

}

Кроме того, попробуйте вместо этого использовать AppendText ручного объединения строк.

this.textBox1.AppendText(text);
1
Ragepotato 11 Янв 2010 в 20:12
+1 хотя блокировка, скорее всего, понадобится функции SetText
 – 
Ed James
11 Янв 2010 в 20:15
О, и не будьте глупыми и используйте блокировку экземпляра внутри потока (легко сделать, если вы не обращаете внимания и кошмар найти;))
 – 
Ed James
11 Янв 2010 в 20:16
1
Функция SetText может быть вызвана только из основного потока, поэтому блокировать действительно нечего. Его вызовы SetText не происходят одновременно.
 – 
Jon B
11 Янв 2010 в 20:17

Я согласен с SLaks, что вам следует использовать BackgroundWorker более правильно. Но чтобы «исправить» предоставленный код, одна проблема связана с вызовом Invoke ... вызов должен вызывать тот же метод, который проверяет соответствие требованиям, что и для согласования потока с создателем формы. Обычно я делаю что-то подобное следующему (использование аргументов может быть несовместимо, но все остальное в порядке). Честно говоря, скорее всего, должен быть только один обработчик, поскольку одновременно может писать только один поток.

  void backgroundWorkerTx_DoWork(object sender, DoWorkEventArgs e)
  {
     if (this.InvokeRequired)
     {
        this.BeginInvoke(new EventHandler<DoWorkEventArgs>(backgroundWorkerTx_DoWork), sender, e);
        return;
     }
     //The text you wish to set should be supplied through the event arguments
     SetText((string)e.Argument);
  }
0
Jamie Altizer 11 Янв 2010 в 20:40