1

I'm creating an application(logviewer) which will display the logs from xml files. Note: each xml file can have one or more records.my application has to show each record as one row in DatagridView control.

basically it will perform the following tasks:

1.from the Do_Work => parse each xml file and add the records to the List.
2.if the List size reaches 100 then call the ProgressChanged event to update the UI (DataGridView) with 100 records.
3.repeat this process untill all records from all xml files are appended to the UI (DataGridView)

requirements: even if the user is trying to read some thousands of files, UI should not freeze.

i have implemented the above scenario by waiting for 100 milliseconds in the DoWork event before calling the ProgressChanged for following reason:

1.Background thread waits (Thread.Sleep(100))for 100 milliseconds so that UI Thread can be updated meanwhile and visible to the User(records are appending).

do i need to have the Thread.Sleep() to make the UI thread to render the records. is there any best approach through which i can update the UI without freezing?

because if i test my application with 4 thousand records,then 100 milliseconds waiting time also not working , i mean application freezes if i do some operations on the form.

but if i increase the waiting time to 500 ms then it works but it takes more time to display all the records.

so please suggest me the better approach in updating the UI while working with BackgroundWorker component.

here is my code:

Note: this is sample code

    private void bWorkerReadXmlLogs_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {                               
            //declare a xmlLogRecords list to hold the list of log records to be displayed on the UI
            List<XmlLogRecord> lstXmlLogRecords = new List<XmlLogRecord>();

            //loop through the records sent by the GetLogDetails() function and add it to the list
            foreach (XmlLogRecord record in GetLogDetails(path))
            {
                //cancel the background thread if the cancel was requested from the user.
                if (bWorkerReadXmlLogs.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
                //add log record to the list
                lstXmlLogRecords.Add(record);

                /*if the list contains 100 items then invoke the progresschanged event 
                where it appends the 100 items into the LogViewer UI (DataGridView)*/
                if (lstXmlLogRecords.Count % 100 == 0)
                {
                    //block/wait on background thread so that processor allocates some cycles to work on UI/Main thread to update the records on the DatagridView
                    Thread.Sleep(100); //if i wait more time like 500 ms then UI does not freeze but it takes more time

                    bWorkerReadXmlLogs.ReportProgress(0, new List<XmlLogRecord>(lstXmlLogRecords));

                    //clear the List to start/add items from the beginning.
                    lstXmlLogRecords.Clear();
                }
            }

            //invoke the ProgressChanged Event for updating the DataGridView if the List contains less than 100 items greater than 0 items
            if (lstXmlLogRecords.Count > 0)
            {                
                //Invoke ProgressChanged Event to update the records on the DataGridView
                bWorkerReadXmlLogs.ReportProgress(0, lstXmlLogRecords);
            }
        }
        catch (Exception ex)
        {
            Write_Trace(ex.Message);
        }
    }

    private void bWorkerReadXmlLogs_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {           
            try
            {
                var rowIndex = 0;
                if (e.UserState is List<XmlLogRecord>)
                {
                    //Cast the UserState object into the List<XmlLogRecord>
                    var records = e.UserState as List<XmlLogRecord>;

                    //iterate over all the records sent from DoWork event and append the each record into the LogViewer control DataGridView UI
                    foreach (var record in records)
                    {
                        //get the next row Index where the new row has to be placed.
                        rowIndex = dgvLogViewer.Rows.Count;

                        //add an emtpy row to add the empty cells into it
                        dgvLogViewer.Rows.Add();

                        //set the LogViewer properties if not set already
                        if (!IsLogviewerPropertiesSet)
                        {
                            SetLogViewerProperties();
                        }

                        //Disable the Column selection for image columns
                        DisableImageColumnsSelection(rowIndex);

                        //Add Data for normal or text cells into the LogViewer Control (DatagridView)
                        AddLogviewerTextCells(rowIndex, record);

                        //Add Icons in Image Columns into the LogViewer control (DataGridView) 
                        AddLogviewerImageCells(rowIndex, record);
                    }

                    //Sort the LogViewer control (DataGridView) by Datetime column(Index = 2) in Descending order.
                    dgvLogViewer.Sort(dgvLogViewer.Columns[MKeys.DTTIME], ListSortDirection.Descending);
                    dgvLogViewer.Columns[MKeys.DTTIME].HeaderCell.SortGlyphDirection = SortOrder.Descending;
                    if (!IsLogviewerSortingDone)
                    {
                        //call selectedindex changed event of the selected record in the datagridview
                        dgvLogViewer_SelectionChanged(null, null);
                        IsLogviewerSortingDone = true;
                    }
                }
            }
            catch (Exception ex)
            {                   
                Write_Trace(ex.Message);
            }
        }
    }
Sudhakar Tillapudi
  • 25,935
  • 5
  • 37
  • 67
  • What does `BackgroundWorker.ProgressChanged` do? Code you've posted has nothing do do with UI except `bWorkerReadXmlLogs.ReportProgress` – Sriram Sakthivel Dec 16 '14 at 10:13
  • @SriramSakthivel: it will iterate over all 100 records and append each record into `DataGridView` – Sudhakar Tillapudi Dec 16 '14 at 10:13
  • I would suggest using [DataGridView.SuspendLayout](http://msdn.microsoft.com/en-us/library/system.windows.forms.control.suspendlayout(v=vs.110).aspx) while you are updating the grid – bansi Dec 16 '14 at 10:14
  • Can you please post that code? That's the most important indeed. – Sriram Sakthivel Dec 16 '14 at 10:14
  • @bansi `SuspendLayout` isn't for that purpose. It's purpose is to suspend rearranging of child controls when doing batch updates. But `DatagridView` isn't having any children. – Sriram Sakthivel Dec 16 '14 at 10:16
  • @SriramSakthivel: sure , let me update my post. – Sudhakar Tillapudi Dec 16 '14 at 10:16
  • `ReportProgress` is asynchronous, so the updates can be queued and occur all at once. That may cause performance problems. Can you stop sorting the records in `ProgressChanged` and do the sort only once in Completed event? – Sriram Sakthivel Dec 16 '14 at 10:24
  • @SriramSakthivel: the requirement is like : records should be appeared in descending order while loading the records because user can see the latest records though application is running, but its okay. let me check. – Sudhakar Tillapudi Dec 16 '14 at 10:34
  • @SriramSakthivel: Seems to be working fine with `100ms` but i want to know is there any better way of doing this. – Sudhakar Tillapudi Dec 16 '14 at 10:39
  • First you don't have to wait 100ms in the background worker for freeing up CPU cycles. And @SriramSakthivel you are wrong, without SuspendLayout you may face freezes and flickers in UI while updating Datagridview, and DataGridView is one of those controls which have the most child controls in a normal form. – bansi Dec 16 '14 at 10:52
  • @bansi Are you serious? Who are the children of `DataGridView`? Can you enumerate? `DataGridView` is a custom drawn control with no child(only exception is editing control). – Sriram Sakthivel Dec 16 '14 at 10:59
  • This is the [old question](http://stackoverflow.com/questions/17554680/the-datagridview-is-loading-very-slowly-how-to-optimise-adding-of-rows-in-datag) which I've answered, and there are few other good answers. You may find it helpful. – Sriram Sakthivel Dec 16 '14 at 11:14
  • @SriramSakthivel Sorry, mistyped, i meant most layout dependent. DatagridView has only scrollbars as child control. – bansi Dec 16 '14 at 11:17

2 Answers2

0

You dont need the Thread Sleep. If you fire ProgressChanged you can load this 100 records to your GridView DataSource. Then use Update-Methods from GridView.

  1. Add new 100 records to your Datasource which is bound to GridView.

Then:

//Maybe set DataSource again here

GridView.Refresh();

If this doesnt work great take a look at BeginInvoke() and EndInvoke() Methods as well.

Sebi
  • 3,879
  • 2
  • 35
  • 62
0

If you are using .NET Framework 4.5 or later I command using Task-Based Asynchronous Pattern and take advantage of the async and await keywods (http://msdn.microsoft.com/en-us/library/hh191443.aspx). But if not you realy don't need any of the threads to sleep. Just handle the ProgressChanged event of the BackgroundWorker and update the grid.

Teddy
  • 304
  • 5
  • 17
  • That is fine, Just forget about using Thread.Sleep, the UI thread and the BackgroundWorker are actually in a different threads so you don't have to worry about the UI freezing. – Teddy Dec 16 '14 at 10:44