0

I am working in WinForms application and used DataGridView control in my application. Initially,i have loaded the 10000 rows and 50 columns in it. My scenario is that updating the datasource at particular time interval(using Timer).

Problem: The grid has been frozen/gang when performing the action(cell_click, scrolling, etc) while updating the datasource.

How to resolve this issue? Is there any work-around? Please suggest me your ideas.

Here is my code so far:

private void timer1_Tick(object sender, EventArgs e)
    {
        //try
        {
            timer.Stop();
            for (int i = 0; i < 10000; i++)
            {
                var row = r.Next() % 10000;
                for (int col = 1; col < 10; col++)
                {
                    var colNum = r.Next() % 55;
                    if (table != null)
                        table.Rows[row][colNum] = "hi";// r.Next().ToString();
                }
            }
            table.AcceptChanges();
            timer.Start();
        }
    }

Here is an sample output:

[https://drive.google.com/open?id=0B9MOGv1FOt-TQ1BNZWtnRktxeXc]

Thanks.

David C.
  • 1,974
  • 2
  • 19
  • 29
Prithiv
  • 504
  • 5
  • 20
  • How often is this data updating? How long does it typically take to finish? – Broots Waymb Feb 14 '17 at 14:46
  • I'm afraid `DataSet` and `DataGridView` simply aren't designed for tens of thousands of rows being updated in realtime. You probably want a data grid with virtualized rendering instead, and ideally with a data source that isn't this tightly bound to the UI. – Luaan Feb 14 '17 at 15:06
  • Are you using a BindingSource as the DataGridView's DataSource? – TnTinMn Feb 14 '17 at 15:51
  • No, i have used DataTable as DataSource. Please refer the attached sample. – Prithiv Feb 15 '17 at 05:33

2 Answers2

1

One of the solutions is to call Application.DoEvents() during such long running operation. Here is the sample

private void timer1_Tick(object sender, EventArgs e)
{
    //try
    {
        timer.Stop();
        for (int i = 0; i < 10000; i++)
        {
            var row = r.Next() % 10000;
            for (int col = 1; col < 10; col++)
            {
                var colNum = r.Next() % 55;
                if (table != null)
                    table.Rows[row][colNum] = "hi";// r.Next().ToString();
            }
            Application.DoEvents(); //add this line
        }
        table.AcceptChanges();
        timer.Start();
    }
}

Another solution is to move your long running task to a separate thread.

Ilya Ivanov
  • 123
  • 6
  • Thanks. How Application.DoEvents() is working? because my grid didn't updated if i'm using this. but sometimes the grid values are changed only for few columns. – Prithiv Feb 15 '17 at 07:10
  • @Prithiv It basically runs an iteration of the message loop. The main problem with this is that it allows reëntrancy, which is somewhat hard to reason about (it could cause two or more of the `Tick` handlers to run alternately in parallel, for example). And even if you go this way and don't restrict the reëntrancy in any way, you really don't want to do it on every iteration - that can get very slow indeed. Instead, do it only often enough to prevent the application for being non-responding for a significant amount of time (say, 20-200ms). – Luaan Feb 16 '17 at 08:36
0

Try to use a BackgrounWorker.

BackgroundWorker is a helper class in the System.ComponentModel namespace for managing a worker thread. It can be considered a general-purpose implementation of the EAP(The Event-Based Asynchronous Pattern), and provides the following features:

  • A cooperative cancellation model

  • The ability to safely update WPF or Windows Forms controls when the worker completes

  • Forwarding of exceptions to the completion event
  • A protocol for reporting progress
  • An implementation of IComponent allowing it to be sited in Visual Studio’s designer

Bellow you can find an example, please adapt it to your Timer:

class Program
{
  static BackgroundWorker _bw = new BackgroundWorker();

  static void Main()
  {
    _bw.DoWork += bw_DoWork;
    _bw.RunWorkerAsync ("Message to worker");
    Console.ReadLine();
  }

  static void bw_DoWork (object sender, DoWorkEventArgs e)
  {
    // This is called on the worker thread
    Console.WriteLine (e.Argument);        // writes "Message to worker"
    // Perform time-consuming task...


           //update your grid
            for (int i = 0; i < 10000; i++)
            {
                var row = r.Next() % 10000;
                for (int col = 1; col < 10; col++)
                {
                    var colNum = r.Next() % 55;
                    if (table != null)
                        table.Rows[row][colNum] = "hi";r.Next().ToString();
                }
            }
            table.AcceptChanges();

  }
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
Zinov
  • 3,817
  • 5
  • 36
  • 70
  • 3
    I'm pretty sure this is going to cause trouble, since it seems the datagrid is databound to the data table. So you're going to get UI updates on a non-UI thread. – Luaan Feb 14 '17 at 15:04
  • That's why you need to use the BackgroundWorker, to execute an operation on a separate thread while you can still reuse the main thread to update your ui. You can find a good example of it on the msdn https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx – Zinov Feb 14 '17 at 19:43
  • 1
    In the example from msdn, the UI elements are actually updated from the UI thread. That is why the progress is updated through the ReportProgress function which invokes the UI thread. Check it out by putting a breakpoint in the ProgressChanged event handler and check the which thread is executing. You will see, it's the main thread. Thus your sample will cause an InvalidOperationException. – sbecker Feb 15 '17 at 07:30
  • @sbecker please take a deep look into the implementation of that class. I just suggested it because it is the recommend way to handle the UI on windows form and wpf, the code above has to be adapted for the current scenario, but that is the idea at the end. Quoting the msdn: The BackgroundWorker class allows you to run an operation on a separate, dedicated thread. Time-consuming operations like downloads and database transactions can cause your user interface (UI) to seem as though it has stopped responding while they are running. Happy coding!!! – Zinov Feb 15 '17 at 15:39
  • 2
    @Luaan is correct about the UI updates. The DataTable changes trigger RowChanged events that are subscribed to by the DGV code. The only thing that is allowing this to work is that it appears that DGV code must be trapping the InvalidOperationException and eating it. You can see this as the `A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll` messages appear in the Immediate window while debugging. Or you can set the debugger to break when the exception is thrown and inspect the call stack. This effectively makes the update slower. – TnTinMn Feb 15 '17 at 21:31
  • 1
    @Zinov As TnTinMn just confirmed. The BackgroundWorker executes code in a different thread. UI updates from this thread cause an InvalidOperationExcetion. Whether you use BackgroundWrokers, Threads, Tasks or something else. Only the UI thead is allowed to do that. And AFAIK changing the DataSource of a DGV does just that. – sbecker Feb 16 '17 at 06:12
  • 2
    @sbecker & Zinov, I did not wish to imply that this is a bad solution, but rather that it is missing a few steps. To avoid the cross-thread access, you need to suspend the bindings before manipulating the Datatable and resume binding post manipulation. This can be done by retrieving the [CurrencyManager](https://msdn.microsoft.com/en-us/library/system.windows.forms.currencymanager(v=vs.110).aspx#Examples) for the DataTable and calling its SuspendBinding and invoking its ResumeBinding methods. The ResumeBinding must be invoke on the UI thread to prevent an exception. – TnTinMn Feb 16 '17 at 15:27