2010-08-19 2 views
1

товарищи) Я нашел интересное поведение метода Invalidate в многопоточных приложениях. Надеюсь, вы могли бы помочь мне с проблемой ...Задача очереди GDI + Paint

У меня возникают проблемы при попытке аннулировать различные элементы управления за один раз: в то время как они идентичны, одна успешно перекрашивается, а другая - нет.

Ниже приведен пример: У меня есть форма (MysticForm) с двумя панелями (SlowRenderPanel) на ней. Каждая панель имеет таймер и с периодом 50 мс вызывается метод Invalidate(). В методе OnPaint я рисую число текущего вызова OnPaint в центре панели. Но заметьте, что в методе OnPaint System.Threading.Thread.Sleep (50) вызывается для моделирования длительной процедуры рисования.

Таким образом, проблема заключается в том, что добавленная панель сначала реплицирует себя гораздо чаще, чем другая.

using System; 
using System.Drawing; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 

namespace WindowsFormsApplication1 { 
    static class Program { 
     [STAThread] 
     static void Main() { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 
      Application.Run(new MysticForm()); 
     } 
    } 

    public class MysticForm : Form { 
     public SlowRenderPanel panel1; 
     public SlowRenderPanel panel2; 

     public MysticForm() { 
      // add 2 panels to the form 
      Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Left, BackColor = Color.Red, Width = ClientRectangle.Width/2 }); 
      Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Right, BackColor = Color.Blue, Width = ClientRectangle.Width/2 }); 
     } 
    } 

    public class SlowRenderPanel : Panel { 
     // synchronized timer 
     private System.Windows.Forms.Timer timerSafe = null; 
     // simple timer 
     private System.Threading.Timer timerUnsafe = null; 
     // OnPaint call counter 
     private int counter = 0; 

     // allows to use one of the above timers 
     bool useUnsafeTimer = true; 

     protected override void Dispose(bool disposing) { 
      // active timer disposal 
      (useUnsafeTimer ? timerUnsafe as IDisposable : timerSafe as IDisposable).Dispose(); 
      base.Dispose(disposing); 
     } 

     public SlowRenderPanel() { 
      // anti-blink 
      DoubleBuffered = true; 
      // large font 
      Font = new Font(Font.FontFamily, 36); 

      if (useUnsafeTimer) { 
       // simple timer. starts in a second. calls Invalidate() with period = 50ms 
       timerUnsafe = new System.Threading.Timer(state => { Invalidate(); }, null, 1000, 50); 
      } else { 
       // safe timer. calls Invalidate() with period = 50ms 
       timerSafe = new System.Windows.Forms.Timer() { Interval = 50, Enabled = true }; 
       timerSafe.Tick += (sender, e) => { Invalidate(); }; 
      } 
     } 

     protected override void OnPaint(PaintEventArgs e) { 
      string text = counter++.ToString(); 

      // simulate large bitmap drawing 
      System.Threading.Thread.Sleep(50); 

      SizeF size = e.Graphics.MeasureString(text, Font); 
      e.Graphics.DrawString(text, Font, Brushes.Black, new PointF(Width/2f - size.Width/2f, Height/2f - size.Height/2f)); 
      base.OnPaint(e); 
     } 

    } 

} 

Debug информация:

1) Каждая панель имеет логическое значение поля useUnsafeTime (значение ИСТИНА по умолчанию), что позволяет использовать System.Windows.Forms.Timer (ложь) InstEd из System.Threading.Timer (правда). В первом случае (System.Windows.Forms.Timer) все работает нормально. Удаление System.Threading.Sleep-вызов в OnPaint также делает выполнение штрафом.

2) Установка интервала таймера до 25 мс или менее предотвращает повторную перемотку второй панели (в то время как пользователь не изменяет размер формы).

3) Использование System.Windows.Forms.Timer приводит к скорости increasement

4) Принудительное управление для ввода контекста синхронизации (Invoke) не имеет смысла. Я имею в виду, что Invalidate (invalidateChildren = false) является «потокобезопасным» и может иметь различное поведение в различных контекстах.

5) Ничего интересного в сравнении этих двух таймеров от IL ... Они просто используют разные функции WinAPI для Установка и удаление таймеры (AddTimerNative, DeleteTimerNative для Threading.Timer; SetTimer, KillTimer для Windows.Forms.Timer) и Windows.Forms.Timer использует метод WndProc NativeWindow для поднимающегося Tick события

Я использую аналогичный фрагмент код в моем приложение и, к сожалению, нет способа использования System.Windows.Forms.Timer) Я использую многопоточную многопоточную визуализацию изображений двух панелей и вызывается метод Invalidate после завершения рендеринга на каждой панели ...

Было бы здорово, если бы кто-то помог мне понять, что происходит за кулисами и как решить проблему.

P.S. Интересное поведение не так ли? =)

ответ

1

Хорошая демонстрация того, что идет не так, когда вы используете членов управления или формы на фоне потока. Winforms обычно ловит это, но есть ошибка в коде метода Invalidate(). Измените это следующим образом:

timerUnsafe = new System.Threading.Timer(state => { Invalidate(true); }, null, 1000, 50); 

, чтобы устранить исключение.

Другая панель работает медленнее, потому что многие вызовы Invalidate() отменяются событием paint. Это достаточно медленно, чтобы сделать это. Классическая резьбовая гонка. Вы не можете вызвать Invalidate() из рабочего потока, синхронный таймер является очевидным решением.

+0

Я упростил «задачу» ... просто отключите таймеры панели и добавьте эту строку кода в MysticForm ctor новый System.Threading.Timer (state => {Invoke (new MethodInvoker() => {Invalidate (true);}));}, null, 1000, 50); мы не должны видеть ПРОБЛЕМУ, но она все еще существует. не должно быть состояния гонки. Я прав? =) – Mikant

+0

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

+0

(если я правильно понял вас) У меня есть два больших растровых изображения (1000x500) и вы хотите нарисовать их на двух панелях в один момент (например, 2 фотографии, привязанные по ширине), но определяется время вызова Invalidate в другом потоке ... (например, каждые 50 мс он дает фотографии (новые) случайной ширины) – Mikant

1

Invalidate() аннулирует клиентскую область или прямоугольник (InvalidateRect()) и «сообщает» Windows, что следующий раз Краски для Windows; освежи меня, нарисуй меня. Но это не вызвать или вызвать сообщение с краской. Чтобы заставить событие рисования, вы должны заставить окна рисовать после вызова Invalidate. Это не всегда необходимо, но иногда это то, что нужно сделать.

Чтобы заставить краску использовать функцию Update(). «Заставляет элемент управления перерисовывать недействительные регионы в пределах своей клиентской области».

Вы должны использовать оба варианта в этом случае.


Edit: Общий метод, чтобы избежать такого рода проблем хранить все краски процедуры и все, что связано с одной (как правило, основной) нити или таймера. Логика может работать в другом месте, но там, где выполняются фактические вызовы краски, все они должны быть в одном потоке или таймере.

Это делается в играх и 3D-симуляциях.

НТН

+0

JustBoo: это приводит к еще более странным вещам: форма не отвечает, первая панель реплики ~ 10-20 раз, затем другая и так далее. Я думаю, что главное для нас - понять реальную разницу между двумя таймерами, потому что в случае System.Windows.Forms.Timer система отлично работает ... с тем же кодом «Invalidate()» ... – Mikant

+0

См. Новое редактирование. (Больше текста для удовлетворения фильтра.) – JustBoo

+0

, но что может быть проще, чем рисовать два растровых изображения на двух панелях с некоторой частотой ... У меня уже есть растровые изображения и невысокая производительность только в Graphics.DrawImageUnscaled ... так как я могу изменить код просто для того, чтобы вызвать перерисовку для обоих в тот момент? = ( – Mikant

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