Как вы обновляете сетку данных (или любой другой элемент управления UI) непрерывно, не замораживая UI? - PullRequest
7 голосов
/ 24 ноября 2011

В приложении WinForms у меня есть сетка данных, связанная с источником данных.Когда данные поступают через фоновый поток, набор данных необходимо обновить, что, в свою очередь, автоматически обновляет сетку данных.Теперь обновления могут быть в порядке, скажем, 7000 обновлений в 20 секунд.Проблема в том, что пользовательский интерфейс зависает, когда такое обновление происходит, потому что оно должно происходить в основном потокеЕсть ли известное решение этой проблемы?

Как вообще можно создавать высокопроизводительные корпоративные приложения в WinForms, где пользовательский интерфейс постоянно обновляется без зависания приложения?


ДобавлениеСценарий, чтобы объяснить это:

Рассмотрим этот сценарий.У вас есть древовидное представление, которое вы используете для представления некоторых иерархических данных.Теперь обновление данных на дереве асинхронно.Сервер может публиковать одно или 1000 обновлений одновременно.Обновление может быть модификацией существующего элемента или добавлением новых узлов.Следует отметить, что обновление не может быть отложено.Узлы где-то представляют сущность реального времени. Задержка обновления даст пользователю представление о том, что само событие было отложено. Так что это невозможно сделать.Если бы это было возможно (с точки зрения бизнес-логики), я бы сделал это очень давно.

Здесь есть ключевой момент: все данные не должны быть видны одновременно.

Чтобы люди больше этого не предлагали:

Добавление фонового рабочего потока НЕ ​​ПОМОЖЕТ, потому что поток должен переключиться на основной поток, чтобы выполнить обновление.Рабочий поток не будет иметь никакого значения.

Ответы [ 14 ]

6 голосов
/ 23 января 2012

Вы не можете, если не хотите использовать DirectX.

Windows Forms не предназначен для отображения информации в режиме реального времени. Как отмечали многие другие, вы можете подойти очень близко, но благодаря тому, как работает цикл сообщений Windows , , вы абсолютно не можете гарантировать , что то, что на экране, будет "реальным -time ", даже если вы создаете таймер, который работает на частоте 60 Гц. Даже если вы сделаете это на основе событий, Windows все равно поставит в очередь сообщение WM_PAINT, которое неизбежно будет запаздывать, если вы ищете отображение в реальном времени.

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

Объяснение того, почему цикл сообщений Windows не работает для отображения в реальном времени, и что такое игровой цикл, см. В: http://www.mvps.org/directx/articles/writing_the_game_loop.htm

Компьютерная игра не может иметь ощутимой задержки, поэтому большинство компьютерных игр пытаются оптимизировать производительность так, чтобы они приближались к частоте кадров на уровне или выше святого Грааля в 60 Гц. (Фильмы проецируются только с частотой 24 Гц, вы считаете, что они «задерживаются»?)

Написание приложения с дисплеем в реальном времени нетривиально , и я настоятельно рекомендую рассмотреть возможность компромисса с тем, что обеспечивает Windows, любым из следующих способов:

  • Создайте таймер, который ставит в очередь обновления экрана с приемлемой скоростью (10 или более раз в секунду). Пользователь не будет воспринимать событие как задержанное, потому что пользователь не может воспринимать задержки, которые происходят за небольшую долю секунды.
  • Вызывает событие, когда изменяются базовые данные, и позволяет Windows решать, когда обновлять дисплей (это будет практически всегда приемлемо).
  • Если возможно, придумайте альтернативный дисплей, который не основан на сетке. Возможно, консоль прокрутки или какой-либо другой интерфейс, который отображает соответствующую информацию без перезаписи старой информации. Это может быть неприменимо, но придумывание другой идеи интерфейса часто является хорошим подходом, когда требуемый интерфейс не будет работать.

Если вам действительно очень нужен очень высокопроизводительный пользовательский интерфейс и вы написали игровой цикл, вы можете сделать это в C # и самостоятельно нарисовать сетку на поверхности DirectX. Как только вы освоите DirectX, нарисовать сетку довольно просто, это всего лишь несколько линий. При таком подходе вы избежите работы с циклом сообщений Windows и, возможно, приблизитесь к производительности в реальном времени.

Вот отличное руководство по использованию DirectX и визуализации в форме Windows:

http://www.godpatterns.com/2005/02/using-directx-and-c-sharp-to-create.html

4 голосов
/ 19 января 2012

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

Что вам нужно сделать, это реализовать (двойную) буферизацию, сообщать о ходе выполнения в буфер и синхронизировать только буфер с GUI время от времени.

Далее следует псевдокод:

DataGrid Grid; // This displays the data
List<object> DataBuffer; // Frequent updates are performed on this list

void BackgroundThreadLoop()
{
   while(true) // This loop iterates 7000 times in 20 seconds
   {
       var result = DoSomeHeavyCalculations();

       // Depending on the nature of the result, you can either just add it to list
       // or perhaps modify existing entries in the list in some way.
       DataBuffer.Add(result); // The simple case
       PerformSomeUpdating(DataBuffer, result); // The complicated case
   }
}

Timer RefreshTimer;
override void OnLoad()
{
    RefreshTimer = new Timer();
    RefreshTimer.Interval = 500; // easy to experiment with this
    RefreshTimer.Tick += (s, ea) => DrawBuffer(DataBuffer);
}

void DrawBuffer(List<object> DataBuffer)
{
    // This should copy DataBuffer and put it in the grid as fast as possible.

    // How to do this really depends on how the list changes and what it contains.
    // If it's just a list of strings:
    Grid.DataSource = DataBuffer.ToList(); // Shallow copy is OK with strings

    // If it's a list of some objects that have meaningful Clone method:
    Grid.DataSource = DataBuffer.Select(o => o.Clone).ToList();

    // If the number of elements is like constant and only some values change,
    // you could use some Dictionary instead of List and just copy values.
}

Если вы дадите более точную информацию, я мог бы помочь вам в дальнейшем.

ОБНОВЛЕНИЕ

С новыми подробностями я бы предложил буферизовать отдельного человека изменения внесены в объекты.Наиболее общий способ представления изменения в структуре некоторых объектов - это функция (возможно, без параметров Action).Получая изменения, вы создаете функции обновления, модифицирующие непосредственно привязанные к виду данные, и сохраняете их в буфере:

List<Action> UpdateBuffer;
void OnUpdateReceived(MyType objToModify, object newValue)
{
    // The point is to make the lambda (below) as efficient as you can; 
    // finding the object and preparing the update should be done here, so that
    // no time is wasted during redraw in the main thread.

    UpdateBuffer.Add(() => objToModify.ApplyNewValueInSomeWay(newValue));


    // some other method should be constructed to add data to the view, but you get the point
}

Теперь метод DrawBuffer (имя больше не полностью адекватно, но не имеет значения)было бы легко:

void DrawBuffer()
{
    List<Action> bufferCopy;
    lock(UpdateBuffer) // the other thread should also lock the buffer for adding
    {
        bufferCopy = UpdateBuffer.ToList();
        UpdateBuffer.Clear();
    }
    view.SuspendLayout();
    foreach(Action a in bufferCopy)
        a();
    view.ResumeLayout();
}

Очевидно, что я не пробовал это точное решение, но оно дает вам возможность контролировать частоту перерисовки и перерисовывать целые партии вместо отдельных обновлений.

1 голос
/ 24 января 2012

Является ли узким местом обработка данных с сервера или фактическая передача их в DataGridView?Если последнее, VirtualMode может помочь вам: http://msdn.microsoft.com/en-us/library/2b177d6d.aspx.

1 голос
/ 18 января 2012

Я только что создал пример приложения, которое заполняет свой внутренний список через BackgroundWorker, и данные будут отображаться в DataGridView. Вы можете изменить скорость вставок, чтобы узнать, соответствует ли она вашим требованиям:

Самой интересной частью должен быть код внутри самой формы:

public partial class FormMain : Form
{
    private List<Person> _Persons;
    private Random _Random;
    private int _TimeoutBetweenInserts;

    public FormMain()
    {
        InitializeComponent();

        // Initialize our private fields
        _Random = new Random();
        _Persons = new List<Person>();
        _TimeoutBetweenInserts = (int)numericUpDownTimeoutBetweenInserts.Value;

        // Attach the list to the binding source and get informed on list changes.
        personBindingSource.DataSource = _Persons;
        personBindingSource.ListChanged += (sender, e) => labelDataGridViewCount.Text = _Persons.Count.ToString();
    }

    private void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
    {
        var spinner = new SpinWait();
        var worker = (BackgroundWorker)sender;

        // Should we abort our adding?
        while (!worker.CancellationPending)
        {
            // Create a new entry ...
            var person = new Person();

            person.Index = _Persons.Count;
            person.Born = new DateTime(_Random.Next(1950, 2012), _Random.Next(1, 13), _Random.Next(1, 28));
            person.FirstName = "Hello";
            person.LastName = "World";

            // ... and add it to the list
            _Persons.Add(person);

            // Do a little waiting ... (to avoid blowing out the list)
            for (int i = 0; i < _TimeoutBetweenInserts; i++)
            {
                spinner.SpinOnce();
            }

            spinner.Reset();
        }

    }

    private void OnBackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // Stop the gui updater, cause the background worker also stopped.
        timerGuiUpdater.Stop();
    }

    private void OnCheckBoxToggleWorkerCheckedChanged(object sender, EventArgs e)
    {
        // Update the "button" according to the state
        checkBoxToggleWorker.Text = checkBoxToggleWorker.Checked ? "&Pause" : "&Start";

        if (checkBoxToggleWorker.Checked)
        {
            if (!backgroundWorker.IsBusy)
            {
                // Start the gui updater and the background worker
                timerGuiUpdater.Start();
                backgroundWorker.RunWorkerAsync();
            }
        }
        else
        {
            // Stop the background worker
            backgroundWorker.CancelAsync();
        }
    }

    private void OnNumericUpDownTimeoutBetweenInsertsValueChanged(object sender, EventArgs e)
    {
        // Update the internal value, to let it propagate into the background worker
        _TimeoutBetweenInserts = (int)numericUpDownTimeoutBetweenInserts.Value;
    }

    private void OnTimerGuiUpdaterTick(object sender, EventArgs e)
    {
        // Tell the BindingSource it should inform its clients (the DataGridView)
        // to update itself
        personBindingSource.ResetBindings(false);
    }
}

Чтобы предоставить вам доступ ко всем этим полям в форме, вот вам Designer.cs:

partial class FormMain
{
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.components = new System.ComponentModel.Container();
        this.dataGridView = new System.Windows.Forms.DataGridView();
        this.Index = new System.Windows.Forms.DataGridViewTextBoxColumn();
        this.lastNameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
        this.firstNameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
        this.bornDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
        this.ageDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
        this.personBindingSource = new System.Windows.Forms.BindingSource(this.components);
        this.backgroundWorker = new System.ComponentModel.BackgroundWorker();
        this.labelDataGridViewCountText = new System.Windows.Forms.Label();
        this.labelDataGridViewCount = new System.Windows.Forms.Label();
        this.labelSpinsBetweenInsertsText = new System.Windows.Forms.Label();
        this.numericUpDownTimeoutBetweenInserts = new System.Windows.Forms.NumericUpDown();
        this.checkBoxToggleWorker = new System.Windows.Forms.CheckBox();
        this.timerGuiUpdater = new System.Windows.Forms.Timer(this.components);
        ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).BeginInit();
        ((System.ComponentModel.ISupportInitialize)(this.personBindingSource)).BeginInit();
        ((System.ComponentModel.ISupportInitialize)(this.numericUpDownTimeoutBetweenInserts)).BeginInit();
        this.SuspendLayout();
        // 
        // dataGridView
        // 
        this.dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
        | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
        this.dataGridView.AutoGenerateColumns = false;
        this.dataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
        this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
        this.dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
        this.Index,
        this.lastNameDataGridViewTextBoxColumn,
        this.firstNameDataGridViewTextBoxColumn,
        this.bornDataGridViewTextBoxColumn,
        this.ageDataGridViewTextBoxColumn});
        this.dataGridView.DataSource = this.personBindingSource;
        this.dataGridView.Location = new System.Drawing.Point(12, 12);
        this.dataGridView.Name = "dataGridView";
        this.dataGridView.Size = new System.Drawing.Size(560, 212);
        this.dataGridView.TabIndex = 0;
        // 
        // Index
        // 
        this.Index.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
        this.Index.DataPropertyName = "Index";
        this.Index.HeaderText = "Index";
        this.Index.Name = "Index";
        this.Index.Width = 58;
        // 
        // lastNameDataGridViewTextBoxColumn
        // 
        this.lastNameDataGridViewTextBoxColumn.DataPropertyName = "LastName";
        this.lastNameDataGridViewTextBoxColumn.HeaderText = "LastName";
        this.lastNameDataGridViewTextBoxColumn.Name = "lastNameDataGridViewTextBoxColumn";
        // 
        // firstNameDataGridViewTextBoxColumn
        // 
        this.firstNameDataGridViewTextBoxColumn.DataPropertyName = "FirstName";
        this.firstNameDataGridViewTextBoxColumn.HeaderText = "FirstName";
        this.firstNameDataGridViewTextBoxColumn.Name = "firstNameDataGridViewTextBoxColumn";
        // 
        // bornDataGridViewTextBoxColumn
        // 
        this.bornDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
        this.bornDataGridViewTextBoxColumn.DataPropertyName = "Born";
        this.bornDataGridViewTextBoxColumn.HeaderText = "Born";
        this.bornDataGridViewTextBoxColumn.Name = "bornDataGridViewTextBoxColumn";
        this.bornDataGridViewTextBoxColumn.Width = 54;
        // 
        // ageDataGridViewTextBoxColumn
        // 
        this.ageDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
        this.ageDataGridViewTextBoxColumn.DataPropertyName = "Age";
        this.ageDataGridViewTextBoxColumn.HeaderText = "Age";
        this.ageDataGridViewTextBoxColumn.Name = "ageDataGridViewTextBoxColumn";
        this.ageDataGridViewTextBoxColumn.ReadOnly = true;
        this.ageDataGridViewTextBoxColumn.Width = 51;
        // 
        // personBindingSource
        // 
        this.personBindingSource.DataSource = typeof(WindowsFormsApplication.Person);
        // 
        // backgroundWorker
        // 
        this.backgroundWorker.WorkerSupportsCancellation = true;
        this.backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.OnBackgroundWorkerDoWork);
        this.backgroundWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.OnBackgroundWorkerRunWorkerCompleted);
        // 
        // labelDataGridViewCountText
        // 
        this.labelDataGridViewCountText.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
        this.labelDataGridViewCountText.Location = new System.Drawing.Point(12, 230);
        this.labelDataGridViewCountText.Name = "labelDataGridViewCountText";
        this.labelDataGridViewCountText.Size = new System.Drawing.Size(50, 23);
        this.labelDataGridViewCountText.TabIndex = 1;
        this.labelDataGridViewCountText.Text = "Count:";
        this.labelDataGridViewCountText.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
        // 
        // labelDataGridViewCount
        // 
        this.labelDataGridViewCount.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
        this.labelDataGridViewCount.Location = new System.Drawing.Point(68, 230);
        this.labelDataGridViewCount.Name = "labelDataGridViewCount";
        this.labelDataGridViewCount.Size = new System.Drawing.Size(82, 23);
        this.labelDataGridViewCount.TabIndex = 2;
        this.labelDataGridViewCount.Text = "0";
        this.labelDataGridViewCount.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
        // 
        // labelSpinsBetweenInsertsText
        // 
        this.labelSpinsBetweenInsertsText.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
        this.labelSpinsBetweenInsertsText.Location = new System.Drawing.Point(265, 230);
        this.labelSpinsBetweenInsertsText.Name = "labelSpinsBetweenInsertsText";
        this.labelSpinsBetweenInsertsText.Size = new System.Drawing.Size(155, 23);
        this.labelSpinsBetweenInsertsText.TabIndex = 3;
        this.labelSpinsBetweenInsertsText.Text = "Spins between inserts:";
        this.labelSpinsBetweenInsertsText.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
        // 
        // numericUpDownTimeoutBetweenInserts
        // 
        this.numericUpDownTimeoutBetweenInserts.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
        this.numericUpDownTimeoutBetweenInserts.Increment = new decimal(new int[] {
        10,
        0,
        0,
        0});
        this.numericUpDownTimeoutBetweenInserts.Location = new System.Drawing.Point(426, 233);
        this.numericUpDownTimeoutBetweenInserts.Maximum = new decimal(new int[] {
        500,
        0,
        0,
        0});
        this.numericUpDownTimeoutBetweenInserts.Minimum = new decimal(new int[] {
        10,
        0,
        0,
        0});
        this.numericUpDownTimeoutBetweenInserts.Name = "numericUpDownTimeoutBetweenInserts";
        this.numericUpDownTimeoutBetweenInserts.Size = new System.Drawing.Size(65, 20);
        this.numericUpDownTimeoutBetweenInserts.TabIndex = 4;
        this.numericUpDownTimeoutBetweenInserts.ThousandsSeparator = true;
        this.numericUpDownTimeoutBetweenInserts.Value = new decimal(new int[] {
        500,
        0,
        0,
        0});
        this.numericUpDownTimeoutBetweenInserts.ValueChanged += new System.EventHandler(this.OnNumericUpDownTimeoutBetweenInsertsValueChanged);
        // 
        // checkBoxToggleWorker
        // 
        this.checkBoxToggleWorker.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
        this.checkBoxToggleWorker.Appearance = System.Windows.Forms.Appearance.Button;
        this.checkBoxToggleWorker.Location = new System.Drawing.Point(497, 230);
        this.checkBoxToggleWorker.Name = "checkBoxToggleWorker";
        this.checkBoxToggleWorker.Size = new System.Drawing.Size(75, 23);
        this.checkBoxToggleWorker.TabIndex = 6;
        this.checkBoxToggleWorker.Text = "&Start";
        this.checkBoxToggleWorker.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
        this.checkBoxToggleWorker.UseVisualStyleBackColor = true;
        this.checkBoxToggleWorker.CheckedChanged += new System.EventHandler(this.OnCheckBoxToggleWorkerCheckedChanged);
        // 
        // timerGuiUpdater
        // 
        this.timerGuiUpdater.Tick += new System.EventHandler(this.OnTimerGuiUpdaterTick);
        // 
        // FormMain
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(584, 262);
        this.Controls.Add(this.checkBoxToggleWorker);
        this.Controls.Add(this.numericUpDownTimeoutBetweenInserts);
        this.Controls.Add(this.labelSpinsBetweenInsertsText);
        this.Controls.Add(this.labelDataGridViewCount);
        this.Controls.Add(this.labelDataGridViewCountText);
        this.Controls.Add(this.dataGridView);
        this.MinimumSize = new System.Drawing.Size(600, 300);
        this.Name = "FormMain";
        this.Text = "DataGridView Performance Tester";
        ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).EndInit();
        ((System.ComponentModel.ISupportInitialize)(this.personBindingSource)).EndInit();
        ((System.ComponentModel.ISupportInitialize)(this.numericUpDownTimeoutBetweenInserts)).EndInit();
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.DataGridView dataGridView;
    private System.ComponentModel.BackgroundWorker backgroundWorker;
    private System.Windows.Forms.BindingSource personBindingSource;
    private System.Windows.Forms.Label labelDataGridViewCountText;
    private System.Windows.Forms.Label labelDataGridViewCount;
    private System.Windows.Forms.Label labelSpinsBetweenInsertsText;
    private System.Windows.Forms.NumericUpDown numericUpDownTimeoutBetweenInserts;
    private System.Windows.Forms.CheckBox checkBoxToggleWorker;
    private System.Windows.Forms.Timer timerGuiUpdater;
    private System.Windows.Forms.DataGridViewTextBoxColumn Index;
    private System.Windows.Forms.DataGridViewTextBoxColumn lastNameDataGridViewTextBoxColumn;
    private System.Windows.Forms.DataGridViewTextBoxColumn firstNameDataGridViewTextBoxColumn;
    private System.Windows.Forms.DataGridViewTextBoxColumn bornDataGridViewTextBoxColumn;
    private System.Windows.Forms.DataGridViewTextBoxColumn ageDataGridViewTextBoxColumn;
}

Последний, но не менее важный класс моего маленького человека, который используется для полезной нагрузки:

public class Person
{
    public int Age
    {
        get
        {
            // ToDo: better algorithm to determine real age is left as an exercise to the reader. ;-)
            var age = (int)((DateTime.Now - Born).TotalDays / 365);
            return Math.Max(0, age);
        }
    }

    public DateTime Born { get; set; }

    public string FirstName { get; set; }

    public int Index { get; set; }

    public string LastName { get; set; }
}
1 голос
/ 18 января 2012

Пользовательский интерфейс всегда будет обновляться основным потоком / пользовательским интерфейсом. Так работает WinForms.

То, что вы можете сделать, это предотвратить выполнение слишком большого потока пользовательского интерфейса. Для этого:

  1. Убедитесь, что вы выполняете всю другую обработку в одном или нескольких разных потоках.
  2. Обновлять интерфейс можно только тогда, когда это полезно для пользователя. Я не вижу / не читаю число, которое меняется каждые 3 мс, поэтому пропустите ОТОБРАЖЕНИЕ обновления.

Обратите внимание, что я использую термины ViewModel, View и Model - остаток этого ответа. Я не заставляю вас использовать MVVM, но это облегчает объяснение. Вы можете использовать MVP или MVC таким же образом.

Вы можете создать специальный вид ViewModel, который вызывает событие через x миллисекунд, чтобы проверить наличие «грязных битов» и вызвать соответствующие события PropertyChanged. Это потребует от вас установки «грязных» битов в установщиках свойств и НЕ вызывать событие PropertyChanged в установщиках.

Возможно, еще лучше отслеживать, когда в последний раз обновлялась ViewModel; когда он длиннее, чем x миллисекунд назад, обновите ViewModel из модели, в противном случае не обновляйте. Это гарантирует, что пользовательский интерфейс будет синхронизирован с ViewModel. Но вы должны понимать, что ViewModel не синхронизирован с моделью. Конечно, можно создавать методы для явной синхронизации моделей.

Выбор между этими двумя может зависеть от того, как вы относитесь к отношению View-ViewModel и сколько времени это все стоит.

1 голос
/ 18 января 2012

Вы можете сделать это, используя BackgroundWorker .В методе DoWork вы можете повторять обновление сетки данных.

Чтобы обновить сетку данных из потока, не являющегося пользовательским интерфейсом, вам нужно сделать следующее:

  1. Создать метод расширения, например

    public static class ControlExtensions
    {
        public static void Invoke(this Control control, Action action)
        {
          if (control.InvokeRequired) control.Invoke(new MethodInvoker(action), null);
          else action.Invoke();
        }
    }
    
  2. Обновить сетку данных как (при условии, что dataGrid - это ваш контрольный идентификатор, а dataSource - как ваш источник данных)

    dataGrid.Invoke(() => { dataGrid.DataSource = dataSource; };
    

Надеюсь, это работаетдля вас.

1 голос
/ 24 ноября 2011

Вы используете BackgroundWorker?Поместите код, который заставляет ваше приложение зависать в событии DoWork:

 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
 {
     YourFreezingCodeHere
 }

И запустите backgroundWorker как

backgroundWorker1.RunWorkerAsync();
0 голосов
/ 25 января 2012

На MSDN опубликована статья, касающаяся шаблона асинхронных вызовов для Windows Forms.Надеюсь, это поможет.

0 голосов
/ 25 января 2012

Я рекомендую использовать два слоя для обработки этого сценария.

Уровень данных не в пользовательском интерфейсе:

Этот слой может собирать все обновления из фонового потока и генерировать конечный объект данных (назовем его ObjectX), который является самым последним и наиболее актуальным состоянием данных.Этот слой должен работать в своем собственном потоке, который вообще не повлияет на пользовательский интерфейс.Также после получения любого обновления вы можете иметь одну логическую переменную (назовем ее NewUpdateExist) и установить для нее значение true, которое указывает, что новые изменения были получены.Не забудьте использовать механизм блокировки потока, чтобы установить для этой переменной значение True, чтобы избежать проблем параллелизма.

Уровень синхронизации пользовательского интерфейса:

Этот слой также может работать в отдельном потоке.У вас может быть таймер, который будет срабатывать через определенный интервал (*), чтобы проверять наличие новых данных со времени последнего обновления пользовательского интерфейса, проверяя переменную NewUpdateExist и, если она есть, установить для NewUpdateExist значение false и генерировать новый поднабор данных, которыйтребуется только для отображения на экране (***).Не забудьте использовать потоковую блокировку при создании подмножества данных и обновлении переменной NewUpdateExist.

После генерации вашего подмножества данных вам нужно вызвать другой метод с помощью элемента управления (в потоке пользовательского интерфейса), чтобы применить этот поднабор данных к элементу управления.Здесь поток пользовательского интерфейса будет блокироваться до завершения процесса, поэтому вам нужно сделать этот метод как можно более легким!Все тяжелые вещи должны быть выполнены вне Invoke, и в этом методе должна быть только часть, относящаяся к управлению пользовательским интерфейсом.

(*) Как упоминал в своем комментарии «Ганс Пассант», человеческий глаз может обрабатывать только 50обновляются миллисекунды, но я даже предлагаю увеличить это до 100 мсек.Вы можете получить некоторые идеи из этой ветки: Какая кратчайшая ощутимая задержка ответа приложения?

(**) Сложная задача в этом случае - как обновить свой элемент управления только данными, которыетребуется вместо отправки всех данных сразу в пользовательский интерфейс.Я действительно рекомендую реализовать пользовательские элементы управления для обработки этой части вместо использования стандартных элементов управления;потому что у вас будет полный доступ к тому, как и когда обновлять пользовательский интерфейс, и вы сможете добиться максимальной производительности.Например, в Grid вы можете узнать первый видимый элемент и количество элементов, которые могут отображаться в пользовательском интерфейсе, и просто обновить эту часть, вместо того, чтобы пытаться обновить элемент управления всеми данными.

Извините, я знаю, что должен объяснитьРешение в коротком сообщении, но это самая короткая версия, которую я могу встретить.Надеюсь, это поможет: -)

0 голосов
/ 24 января 2012

re: Проблема в том, что пользовательский интерфейс зависает, когда такое обновление происходит, потому что оно должно происходить в основном потоке.Есть ли известное решение этой проблемы?

нет, как вы видите

В общем, как вы можете разрабатывать высокопроизводительные корпоративные приложения в WinForms с пользовательским интерфейсомпостоянно обновляется без зависания приложения?

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

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