如何在不冻结UI的情况下连续更新数据网格(或任何其他UI控件)?

时间:2011-11-24 11:32:17

标签: c# winforms user-interface

在WinForms应用程序中,我有一个与数据源关联的数据网格。当数据通过后台线程进入时,需要更新数据集,然后自动更新数据网格。现在,更新可以是每20秒更新7000次。 问题是当这样的更新发生时UI会挂起,因为它必须发生在主线程上。这个问题有没有明确的解决方案?

通常,如何在WinForms中设计高性能的企业应用程序,其中UI在不冻结应用程序的情况下持续更新?


添加一个场景来解释这个:

考虑这种情况。您有一个树视图,用于表示某些分层数据。现在树上的数据更新是异步的。服务器可以同时发布一个或1000个更新。更新可以是对现有项的修改或添加新节点。需要注意的是,更新不能延迟。节点代表某个地方的实时实体。 延迟更新会让用户感觉事件本身已经延迟。所以这样做不能完成。如果有可能(从业务逻辑的角度来看)我会在很长一段时间内完成它。

这里有一个关键点:所有数据不需要同时显示。

所以人们不再这样说了:

添加后台工作线程将无法帮助,因为线程必须切换到主线程才能执行更新。工作线程没有区别。

14 个答案:

答案 0 :(得分:6)

您不能,除非您想使用DirectX。

Windows窗体不适用于实时信息显示。正如许多其他人所指出的那样,你可以非常接近,但由于 Windows消息循环的工作方式,你绝对不能保证屏幕上的内容将是“实时”,即使您创建一个滴答为60hz的Timer。即使您以事件驱动的方式执行此操作,Windows仍会排队WM_PAINT消息,如果您正在寻找实时显示,则该消息将不可避免地延迟。

如果你真的想要一个非常接近实时的显示器,你需要实现类似于游戏循环的东西。

有关Windows消息循环无法用于实时显示以及游戏循环的原因的说明,请参阅: http://www.mvps.org/directx/articles/writing_the_game_loop.htm

计算机游戏不能有任何可察觉的延迟,因此大多数计算机游戏都试图优化性能,以便它们接近60hz的圣杯或更高的帧速率。 (电影仅以24hz投影,你认为它们是“延迟”吗?)

使用实时显示编写应用程序非常重要,我强烈建议您考虑通过以下任何方式对Windows提供的内容进行攻击:

  • 创建一个计时器,以可接受的速率(每秒10次或更多次)排列屏幕更新。用户不会将事件视为延迟,因为用户无法察觉在一小段时间内发生的延迟。
  • 在基础数据发生变化时引发事件,让Windows决定何时更新显示(这几乎总是可以接受)。
  • 如果可能,请提供不基于网格的备用显示器。也许是滚动控制台或其他一些显示相关信息而不会覆盖旧信息的界面。这可能不适用,但是当你想要的界面不起作用时,提出另一个界面想法通常是一个很好的方法。

如果你真的,真的还想要一个非常高性能的用户界面,并编写一个游戏循环,你可以在C#中这样做,并自己绘制一个网格到DirectX表面。一旦掌握了DirectX,绘制网格相当容易,它只是一堆线。使用这种方法,您将避免处理Windows消息循环,并可能接近实时性能。

这是一个很棒的教程,介绍如何使用DirectX以及如何在Windows窗体上呈现:

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

答案 1 :(得分:4)

在您的评论中,您说繁重的处理过程经常报告进度,您不能删除任何报告(因为报告是需要显示的真实数据)。

你应该做的是实现(双重)缓冲,将进度报告给缓冲区,并且每隔一段时间只将缓冲区与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)。在接收更改时,构造update-functions直接修改视图绑定数据并将它们存储在缓冲区中:

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();
}

显然我没有尝试过这个确切的解决方案,但是它让你能够控制重绘频率并重绘整个批次而不是单个更新。

答案 2 :(得分:1)

您使用的是BackgroundWorker吗?将使应用程序冻结的代码放在DoWork事件中:

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

启动backgroundWorker,如

backgroundWorker1.RunWorkerAsync();

答案 3 :(得分:1)

您可以使用BackgroundWorker执行此操作。在DoWork方法中,您可以迭代更新数据网格。

要从非UI线程更新数据网格,您需要按照以下方式

  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 是您的控件ID, dataSource 是您的数据源)

    dataGrid.Invoke(() => { dataGrid.DataSource = dataSource; };
    
  3. 希望这适合你。

答案 4 :(得分:1)

UI将始终由主/ UI线程更新。这就是WinForms的工作方式。

您可以做的是阻止UI线程做太多。要做到这一点:

  1. 确保在一个或多个不同的线程上执行所有其他处理。
  2. 仅在对用户有用时更新UI。我无法看到/读取每3毫秒更改一次的数字,因此请跳过显示更新。
  3. 请注意,我使用的术语是ViewModel,View和Model是本答案的其余部分。我并没有强迫你使用MVVM,但这使得解释更容易。您可以以相同的方式使用MVP或MVC。

    您可以创建一种特殊类型的ViewModel,它在x毫秒后引发事件以检查“脏位”并引发相应的PropertyChanged事件。这将要求您在属性设置器中设置脏位,而不是在setter中引发PropertyChanged事件。

    或许更好的方法是跟踪ViewModel最后一次更新的时间;当它超过x毫秒之前,从模型更新ViewModel,否则不要。这可以保证UI与ViewModel同步。但您必须意识到ViewModel与模型不同步。当然,可以创建明确同步模型的方法。

    这两者之间的选择可能取决于您对View-ViewModel关系的看法以及这一切需要花费多少时间。

答案 5 :(得分:1)

我刚刚创建了一个示例应用程序,它将通过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; }
}

答案 6 :(得分:1)

是从服务器处理数据还是实际将数据放到DataGridView中的瓶颈?如果是后者,VirtualMode可以帮助您:http://msdn.microsoft.com/en-us/library/2b177d6d.aspx

答案 7 :(得分:0)

Application.DoEvents();

在计时器内使用此方法。

private void timer1_Tick(object sender, EventArgs e)
{
     Application.DoEvents();
}

你应该启动你的用户界面冻结的计时器,或者你可以在form_Load中启动它,并将小数字设置为计时器的间隔,以使其经常打勾。例如,将其设置为十。

timer1.Start();
timer1.Interval = 10;

答案 8 :(得分:0)

我已经完成了很多高容量数据传输(每秒数百次),我认为DataGrid不是你想要的控件。它旨在呈现数据并让用户编辑它,它并没有真正优化为信息流。在这个卷上,实时查看数据对用户来说不会太好,它只是一个数据流太大而且速度快。

我建议您继续使用后台工作人员来完成工作(就像您说的那样)并使用ReportProgress方法将%完成发送回进度条。您还可以使用您正在处理的文件更新页面上的标签。标签将自动更新,不会冻结您的UI。为此,请在后台工作程序调用的类中创建实例变量。在UI上,创建该类的实例,并在后台worker的ProgressChanged方法中将UI标签设置为类实例变量。每次调用backgroundworker.ReportProgress()

时它都会更新

然后将所有信息放在日志中,以便以后有人可以查看。试图在视觉上接受350次/秒的改变并不是那么有益。

答案 9 :(得分:0)

此问题的一个解决方案是定期更新数据模型,即每隔x毫秒从通信线程批量更新它们。有关如何访问服务器数据的更多信息将有助于为您提供更明确的建议。

同时您应该使用虚拟化控件(尤其是数据网格)。使用虚拟网格基本上意味着可视单元格在运行中呈现。因此,您只需要更新当前显示的数据。当每个单元变得可见时,它将访问数据模型以获得当时的相关值。请参阅this link作为实施虚拟网格的起点。

通过组合这两种方法,您应该能够最大限度地减少对网格的更新量。

答案 10 :(得分:0)

re:问题是当这样的更新发生时UI会挂起,因为它必须在主线程上发生。是否有针对此问题的已知解决方案?

不,正如你所见

一般来说,如何在WinForms中设计高性能的企业应用程序,其中UI在不冻结应用程序的情况下持续更新?

在您描述的规模上,您不能。尝试限制UI控件的范围,以便它不会尝试同时显示所有地方发生的一切,但会强制用户选择一个可以以可接受的速率更新的区域。

答案 11 :(得分:0)

我建议使用两个图层来处理这种情况。

非UI数据层:

该层可以从后台线程获取所有更新,并生成最终的Data对象(让我们称之为ObjectX),这是最新和最新的数据状态。该层应该在它自己的线程上运行,这根本不会影响UI。此外,在收到任何更新后,您可以拥有一个布尔变量(让我们称之为NewUpdateExist)并将其设置为true,表示已收到新的更改。不要忘记使用线程安全锁定机制将此变量设置为True,以避免并发问题。

UI同步层:

此图层也可以在分离的线程上运行。你可以有一个定时器,它会在特定的时间间隔(*)触发,通过检查NewUpdateExist变量检查自上次UI更新以来是否有任何新数据,如果有,则将NewUpdateExist设置为false并生成新的数据子集只需要在屏幕上显示(***)。在生成数据子集和更新NewUpdateExist变量时,不要忘记使用线程安全锁定。

在生成数据子集之后,您需要通过控件(在UI线程中)调用另一个方法来将此子数据集应用于控件。这是UI线程将阻塞直到进程完成的地方,因此您需要使此方法尽可能轻松!所有繁重的东西都需要在Invoke之外完成,只有与UI控件相关的部分需要在该方法中。

(*)正如他的评论中提到的“Hans Passant”,人眼只能处理50毫秒的刷新,但我甚至建议将其增加到100毫秒。你可以从这个帖子中得到一些想法: What is the shortest perceivable application response delay?

(**)在这种情况下,棘手的部分是如何仅使用所需的数据来更新控件,而不是一次性将所有数据推送到UI。我真的建议实现自定义控件来处理这个部分,而不是使用标准控件;因为您可以完全访问更新UI的方式和时间,并且可以获得最佳性能。例如,在网格上,您可以找到可在UI上显示的第一个可见项目和项目数量,只需更新该部分,而不是尝试使用所有数据更新控制。

抱歉,我知道我想在短信中解释解决方案,但这是我遇到的最短版本。我希望这会有所帮助: - )

答案 12 :(得分:0)

MSDN上发布了一篇关于Windows窗体的异步调用模式的文章。我希望这会有所帮助。

答案 13 :(得分:-1)

使用backgroundWorker,它会在单独的线程中运行插入代码,因此应用程序不会冻结。

 public void backgroundWorkerPinger_DoWork(object sender, DoWorkEventArgs e)
        {

            Ping ping = new Ping();

                try
                {
                    PingReply pingreply = ping.Send("46.4.106.10", 500);
                    string active = pingreply.Status.ToString();
                    if (active == "Success")
                    {
                        //Pokud je spojení aktivni pak se nastavi barva labelu na zelenou a vypise se aktivni
                        ActiveOrNotLabel.ForeColor = Color.Green;
                        ActiveOrNotLabel.Text = "Aktivní";
                       // MessageBox.Show("vyjimka2");
                        if (connection_enabled == false)
                        {
                            admini.Enabled = true;
                            connection_enabled = true;
                        }
                    }
                    if (active != "Success") {
                        ActiveOrNotLabel.ForeColor = Color.Red;
                        ActiveOrNotLabel.Text = "Neaktivní";
                        admini.Enabled = false;
                        connection_enabled = false;

                     }
                }
                catch
                {
                    //Jinak na cervenou a neaktivni

                    //MessageBox.Show("vyjimka");
                    ActiveOrNotLabel.ForeColor = Color.Red;
                    ActiveOrNotLabel.Text = "Neaktivní";
                    admini.Enabled = false;
                    connection_enabled = false;

                }
            }