2015-10-04 3 views
1

Я пытаюсь вызвать метод в основном потоке, который будет обновлять несколько элементов пользовательского интерфейса. Один из этих элементов - RichTextView. Я нашел 3 метода для обновления UI, которые все три из них после запуска на некоторое время, сбой со следующей ошибкой. Как только я изменил RichTextView на простое текстовое поле, тип 2 больше не будет разбиваться (я все еще не уверен, что это так).SynchronizationContext или Invoke

Необработанное исключение типа 'System.StackOverflowException' произошло в System.Windows.Forms.dll

StackOverflowException

Мой упрощенный код

// Type 1 
    private readonly SynchronizationContext synchronizationContext; 

    public Form1() { 
     InitializeComponent(); 
     // Type 1 
     synchronizationContext = SynchronizationContext.Current; 
    } 

    //Type 3 
    public void Log1(object message) { 
     Invoke(new Log1(Log), message); 
    } 

    public void Log(object message) { 
     if (this.IsDisposed || edtLog.IsDisposed) 
      return; 

     edtLog.AppendText(message.ToString() + "\n"); 
     edtLog.ScrollToCaret(); 
     Application.DoEvents(); 
    } 

    private void btnStart_Click(object sender, EventArgs e) { 

     for (int i = 0; i < 10000; i++) { 
      ThreadPool.QueueUserWorkItem(Work, i); 
     } 
     Log("Done Adding"); 
    } 

    private void Work(object ItemID) { 

     int s = new Random().Next(10, 15);// Will generate same random number in different thread occasionally 

     string message = ItemID + "\t" + Thread.CurrentThread.ManagedThreadId + ",\tRND " + s; 

     //Type1 
     synchronizationContext.Post(Log, message); 

     // Type 2 
     //Invoke(new Log1(Log), message); 

     // Type 3 
     //Log1(message); 

     Thread.Sleep(s); 
    } 

Полный код

using System; 
using System.Threading; 
using System.Windows.Forms; 

namespace Test { 

    public delegate void Log1(string a); 

    public partial class Form1 : Form { 

     private System.ComponentModel.IContainer components = null; 
     private Button btnStart; 
     private TextBox edtLog; 

     protected override void Dispose(bool disposing) { 
      if (disposing && (components != null)) { 
       components.Dispose(); 
      } 
      base.Dispose(disposing); 
     } 

     private void InitializeComponent() { 
      this.btnStart = new Button(); 
      this.edtLog = new TextBox(); 
      this.SuspendLayout(); 

      this.btnStart.Anchor = (AnchorStyles.Top | AnchorStyles.Right); 
      this.btnStart.Location = new System.Drawing.Point(788, 12); 
      this.btnStart.Size = new System.Drawing.Size(75, 23); 
      this.btnStart.Text = "Start"; 
      this.btnStart.Click += new System.EventHandler(this.btnStart_Click); 

      this.edtLog.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right); 
      this.edtLog.Location = new System.Drawing.Point(12, 41); 
      this.edtLog.Multiline = true; 
      this.edtLog.ScrollBars = ScrollBars.Vertical; 
      this.edtLog.Size = new System.Drawing.Size(851, 441); 

      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 
      this.ClientSize = new System.Drawing.Size(875, 494); 
      this.Controls.Add(this.edtLog); 
      this.Controls.Add(this.btnStart); 
      this.ResumeLayout(false); 
      this.PerformLayout(); 
     } 

     [STAThread] 
     static void Main() { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 
      Application.Run(new Form1()); 
     } 


     // Type 1 
     private readonly SynchronizationContext synchronizationContext; 

     public Form1() { 
      InitializeComponent(); 
      // Type 1 
      synchronizationContext = SynchronizationContext.Current; 
     } 

     //Type 3 
     public void Log1(object message) { 
      Invoke(new Log1(Log), message); 
     } 

     public void Log(object message) { 
      if (this.IsDisposed || edtLog.IsDisposed) 
       return; 

      edtLog.AppendText(message.ToString() + "\n"); 
      edtLog.ScrollToCaret(); 
      Application.DoEvents(); 
     } 

     private void btnStart_Click(object sender, EventArgs e) { 

      for (int i = 0; i < 10000; i++) { 
       ThreadPool.QueueUserWorkItem(Work, i); 
      } 
      Log("Done Adding"); 
     } 

     private void Work(object ItemID) { 

      int s = new Random().Next(10, 15);// Will generate same random number in different thread occasionally 

      string message = ItemID + "\t" + Thread.CurrentThread.ManagedThreadId + ",\tRND " + s; 

      //Type1 
      synchronizationContext.Post(Log, message); 

      // Type 2 
      //Invoke(new Log1(Log), message); 

      // Type 3 
      //Log1(message); 

      Thread.Sleep(s); 
     } 

    } 
} 

Вопрос 1

Почему и когда я должен использовать SynchronizationContext или Invoke. В чем разница (исправьте меня, если я ошибаюсь, поскольку я бегу на winform, SynchronizationContext.Current всегда существует)?

Вопрос 2 Почему я получаю ошибку StackOverflow здесь? Я делаю что-то неправильно? Есть ли разница между вызовом вызова в отдельный метод или же рабочий метод (LOG1 происходит сбой во время прямого вызова Invoke не делает)

Вопрос 3

Когда пользователь закрывает приложение до нитки закончить свою работу, I get и exception, говорящий, что Form1 расположен и недоступен при вызове метода Log (любой тип 3). Должен ли я обрабатывать исключения в потоках (включая основной поток)?

ответ

1
Application.DoEvents(); 

Поистине впечатляюще, как этот метод может привести к сбою программы совершенно непостижимыми способами. Основной рецепт, который вам нужен, чтобы заставить его взорвать ваш стек, очень прост. Вам нужен рабочий поток, который вызывает Log1() с высокой скоростью, примерно тысячу раз в секунду или больше. И код, который работает в потоке пользовательского интерфейса, который делает что-то дорогое, занимает больше миллисекунды. Как добавление строки в TextBox.

Тогда:

  • LOG1() вызывает для входа(), который вызывает DoEvents().
  • Позволяет другому Log1() вызывать вызов Log(), который вызывает DoEvents.
  • Позволяет другому Log1() вызывать вызов Log(), который вызывает DoEvents.
  • Позволяет другому Log1() вызывать вызов Log(), который вызывает DoEvents.
  • Позволяет другому Log1() вызывать вызов Log(), который вызывает DoEvents.
  • ....
  • Kaboom!

Вы добавили Application.DoEvents(), потому что заметили, что ваш пользовательский интерфейс застыл, текстовое поле не показывает добавленные строки текста, и через 5 секунд вы получаете сообщение «Не ответив!». призрачное окно. Да, это исправило эту проблему. Но не очень конструктивно, как вы узнали. Что вам хотел это отправить событие Paint для текстового поля. DoEvents() не является достаточно избирательным, это все события. Включая те, на которые вы не рассчитывали, событие, вызванное Invoke(). Просто сделайте это выборочным:

edtLog.Update(); 

Больше не существует StackOverflowException. Но это еще не программа, которая работает. Вы получите безумно прокручиваемое текстовое поле, которое никто не может прочитать. И вы не можете это остановить, программа по-прежнему мертва для пользовательского ввода, поэтому нажатие кнопки «Закрыть» не работает.

Вы еще не указали ошибку basic в вашей программе. Ошибка пожарного шланга, третья наиболее распространенная ошибка при нарезке после гонок и тупиков. Ваша рабочая нить дает результаты со скоростью far выше, чем поток пользовательского интерфейса может их использовать. И, прежде всего, скорость, которую человек может видеть. Вы создали непригодный пользовательский интерфейс.

Исправить эту проблему и все несчастья, которые вы получили от потоковой передачи, исчезнут. Код слишком поддельный, чтобы рекомендовать конкретное исправление, но должен быть где-то рядом только с отображением моментальных снимков, которые вы принимаете один раз в секунду. Или только обновление пользовательского интерфейса, когда рабочий поток завершается, шаблон, рекомендованный BackgroundWorker. Что бы ни потребовалось, чтобы перебалансировать работу, чтобы поток пользовательского интерфейса выполнял меньше работы, чем рабочий поток.

+0

Я не уверен, что вы подразумеваете под огненной жукой (это общий термин?). Я понимаю, что вы имеете в виду. Как синхронизировать потоки, чтобы они блокировались до тех пор, пока основной поток не завершил обновление пользовательского интерфейса перед запуском нового обновления, я думал, что Invoke должен это сделать? На самом деле мне все равно, если текстовое поле прокручивается, однако пользователь должен иметь возможность закрыть приложение, переместить его окно или изменить его размер (что, очевидно, я не знаю, как это сделать). – AaA

+0

Должен ли я добавить Семафор перед вызовом и выпуском после? – AaA

+0

Как и все ошибки с чередованием, ошибка пожарного шланга является основной ошибкой, требующей изменения дизайна вашей программы. Вы * должны * остановить поток пользовательского интерфейса от сжигания 100% ядра. Ничего хорошего не бывает, когда он должен делать столько работы, он выполняет свои обязанности по приоритету. И освобождение очереди вызовов имеет более высокий приоритет, чем реагирование на ввод или рисование пользовательского интерфейса. Вот и все. Если вы хотите синхронизировать, вы должны знать, когда поток пользовательского интерфейса простаивает и готов к работе. Это технически возможно, событие Application.Idle сообщает об этом. –

Смежные вопросы