How do you update a datagrid (or any other user interface control) without freezing the user interface?

In a WinForms application, I have a datagrid associated with a data source. When and when data arrives through a background stream, the data set needs to be updated, which, in turn, automatically updates the datagrid. Now updates can be in order, say, 7000 updates in 20 seconds. The problem is that the user interface freezes when such an update occurs because it needs to happen in the main thread. Is there a known solution to this problem?

Generally, how can you create high-performance enterprise applications in WinForms, where the user interface is constantly updated without freezing the application?


Adding a script to explain this:

Consider this scenario. You have a tree view that you use to represent some hierarchical data. Now updating the data on the tree is asynchronous. The server can publish one or 1000 updates at a time. An update may be a modification of an existing item or the addition of new nodes. It should be noted that the update cannot be delayed. Nodes represent a real-time object somewhere. Delaying the update will give the user an idea that the event itself has been delayed. So this is impossible to do. If it were possible (from the point of view of business logic), I would do it for a long time.

There is a key point: all data should not be visible at the same time.

So that people no longer offer this:

Adding a worker workflow DOES NOT HELP because the thread must switch to the main thread in order to perform the update. Workflow does not matter.

+7
source share
14 answers

Is there a bottleneck in processing data from the server or actually puts it in a DataGridView? If the latter, VirtualMode can help you: http://msdn.microsoft.com/en-us/library/2b177d6d.aspx .

+1
source

You cannot if you do not want to use DirectX.

Windows Forms is not designed to display real-time information. Like many others, you can come very close, but because the Windows message loop works, you absolutely cannot guarantee that what is displayed on the screen is “real-time”, even if you create a timer that ticks at 60 Hz. Even if you take this into account events, Windows will still queue the WM_PAINT message, which will inevitably be delayed if you are looking for a real-time display.

If you really need a display that is very close to real time, you will need to implement something similar to Game Loop .

To explain why the Windows Message loop will not work for real-time display and which game loop, see http://www.mvps.org/directx/articles/writing_the_game_loop.htm

A computer game cannot have any noticeable delay, so most computer games try to optimize performance so that they approach frame rates at or above the 60 Hz holy grail. (Films are projected only for 24 hours, do you think that they are "delayed"?)

Writing an application with a real-time display is non-trivial , and I highly recommend that you consider compromising what Windows provides in one of the following ways:

  • Create a timer that pauses screen refresh at an acceptable speed (10 or more times per second). The user does not perceive the event as delayed, since the user cannot perceive delays that occur after a small fraction of a second.
  • Raise the event when the main data changes, and let Windows decide when to refresh the display (this will almost always be acceptable).
  • If possible, come up with an alternative display that is not grid-based. Perhaps a scrolling console or some other interface that displays relevant information without overwriting old information. This may not be applicable, but coming up with a different interface idea is often a good approach when the interface you want will not work.

If you really really want a very high-performance user interface and write a game loop, you can do it in C # and paint the grid yourself on a DirectX surface. Once you get virtual DirectX, drawing a grid is pretty simple, it's just a bunch of lines. With this approach, you avoid using the Windows message loop and can potentially work in real time.

Here's a great tutorial on how to use DirectX and how to render in Windows form:

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

+6
source

In your commentary, you say that your heavy processing reports on the work done very often, and you cannot discard any report (because the report is real data that needs to be displayed).

What you need to do is implement (double) buffering, report progress to the buffer, and only occasionally synchronize the buffer with the GUI.

Following is the pseudo code:

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 just a list of strings: Grid.DataSource = DataBuffer.ToList(); // Shallow copy is OK with strings // If it 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. } 

If you give more accurate information, I may be able to help further.

UPDATE

With new details, I suggest buffering individual changes made to objects. The most common way of representing a change in the structure of some objects will be a function (possibly without Action parameters). Upon receiving the changes, you create update functions that directly change the data associated with the viewing and save them in the buffer:

 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 } 

Now the DrawBuffer method (the name is no longer completely adequate, but it doesn’t matter) would be simple:

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

Obviously, I have not tried this exact solution, but it gives you the ability to control the frequency of redrawing and redraw whole batches instead of single updates.

+4
source

Are you using BackgroundWorker? Put the code that causes the application to freeze the DoWork event:

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

And run backgroundWorker like

 backgroundWorker1.RunWorkerAsync(); 
+1
source

You can do this using BackgroundWorker . In the DoWork method DoWork you can iterate to update the datagrid.

To update a datagrid from a thread other than a UI, you need the following:

  • Create an extension method like

     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(); } } 
  • Refresh the data grid as (assuming dataGrid is your control identifier and dataSource is your data source)

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

Hope this works for you.

+1
source

The user interface will always be updated using the main / user thread. This is how WinForms works.

What you can do is to prevent excessive user interface flow. For this:

  • Make sure that you do all other processing on one or more different threads.
  • Updating the user interface only when it is useful to the user. I do not see / read a number that changes every 3 ms, so skip the update DISPLAY.

Please note that I use the terms ViewModel, View and Model - the rest of this answer. I do not force you to use MVVM, but it simplifies the explanation. You can use MVP or MVC in the same way.

You can create a special ViewModel that fires an event after x milliseconds to check for dirty bits and raise the corresponding PropertyChanged events. This would require you to set the dirty bits in the property settings and NOT raise the PropertyChanged event in the setters.

It might even be better to keep track of how the ViewModel was last updated; when it is longer than x milliseconds ago, update the ViewModel from the Model, otherwise not. This ensures that the user interface is synchronized with the ViewModel. But you should understand that ViewModel is not synchronized with the model. Of course, you can create methods for directly synchronizing models.

The choice between the two may depend on how you think about the relationship of the View-ViewModel and how much time it all costs.

+1
source

I just made a sample application that populates its internal list via BackgroundWorker and the data will be displayed in the DataGridView. You can change the insertion speed to see if it matches your requirements:

The most interesting part should be the code inside the form itself:

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

To access all of these fields on the form, Designer.cs comes here:

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

The last but not least important class of my little man, which is used for the payload:

 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
source
 Application.DoEvents(); 

Use this method inside a timer.

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

You must start the timer when your user interface freezes, or you can start it in form_Load and set a small number to your timer interval so that it changes frequently. For example, set it to ten.

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

I have done a lot of large volume transfers (hundreds per second) like this, and I think DataGrid is just not the control you want. It is designed to represent data and allows the user to edit it; it is not optimized as an information stream. In this volume, it will not do much good for users to view data in real time, it will simply be a data stream too large and fast to understand.

I suggest that you continue to use the background worker to do the work (for example, you said you were) and use the ReportProgress method to send% done back to the progress bar. You can also update the shortcut on the page with the file you are working on. The label will be updated automatically, and not freeze your user interface. To do this, create an instance variable in the class that calls your background desktop. Create an instance of this class in your user interface, and in the background, the ProgressChanged working method sets your user interface shortcut to your class instance variable. It will be updated every time you call backgroundworker.ReportProgress ()

Then put all the information in a journal so that someone can look at it later. It is simply not that useful to try to visually accept 350 changes / second.

0
source

One solution to this problem is to periodically update your data model, that is, periodically update them from the communication stream every x milliseconds. With a little more information about how you access server data, it would be helpful to give you a more explicit suggestion.

At the same time, you should use virtualized controls (especially datagrid). Using a virtual grid basically means that visible cells are displayed on the fly. Therefore, you only need to update the data currently displayed. As each cell becomes visible, it will access the data model to get the corresponding value at that time. See this link as a starting point for implementing a virtual grid.

By combining these two approaches, you can minimize the number of updates in the grid.

0
source

re: The problem is that the user interface freezes when such an update occurs because it needs to happen in the main thread. Is there a solution to this problem?

no since you see

Generally, how can you create high-performance enterprise applications in WinForms, where the user interface is constantly updated without freezing the application?

On the scale that you describe, you cannot. Try to limit the scope of your user interface so that it does not try to display everything that happens everywhere at once, but forces the user to choose an area to focus on that you can update at an acceptable speed.

0
source

I recommend using two levels to handle this scenario.

Non-UI Data Layer:

This layer can capture all updates from your background thread and generate a final Data object (let it call ObjectX), which is the last and most current state of the data. This layer should run its own thread on it, which will not affect the user interface at all. Also, after receiving any update, you can have one logical variable (let it be called NewUpdateExist) and set it to true, which indicates that new changes have been received. Remember to use the thread locking mechanism to set this variable to True to avoid concurrency issues.

UI Sync Layer:

This layer can also work on a separate branch. You can have a timer that starts at a certain interval (*) to check if there is any new data since the last update of the user interface by checking the NewUpdateExist variable, and if so, set NewUpdateExist to false and generate a new one a subset of the data is only required for display on the screen (***). Remember to use a thread-protected lock when creating a subset of the data and updating the NewUpdateExist variable.

After generating your subset of data, you need to call another method using the control (In UI Thread) to apply this subset of data to the control. Here, the user interface thread is blocked until the process is completed, so you need to make this method as easy as possible! All the hard things need to be done outside of Invoke, and only the part related to user interface management should be in this method.

(*) As Hans Passant mentions in his comment, the human eye can process 50 milliseconds, but I even suggest increasing it to 100 ms. You can get some ideas from this topic: What is the shortest perceived application response delay?

(**) The difficult part in this case is how to update your control only with the data you need, and not immediately move all the data to the user interface. I really recommend using special controls to handle this part instead of standard controls; because you will have full access to how and when to update the user interface, and you can achieve the best performance. For example, in the Grid, you can find out the first visible element and the number of elements that can be displayed in the user interface, and simply update this part instead of trying to update the control with all the data.

Sorry, I know that I am proposing to explain a short message, but this is the shortest version that I may encounter. Hope this helps :-)

0
source

The MSDN page contains an article on the asynchronous call pattern for Windows Forms. Hope this helps.

0
source

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; } } 
-one
source

All Articles