Виртуальный режим Datagridview с большой загрузкой данных

#c# #winforms #multithreading #datagridview

#c# #winforms #многопоточность #datagridview

Вопрос:

Я хочу отобразить «много тяжелых данных» внутри datagridview в виртуальном режиме.

Я использую буфер, в котором я храню n (2) страниц данных, изначально 2 первые страницы хранятся в буфере. Когда просмотр сетки прокручивается до строки, которой не существует в буфере, я загружаю текущую новую страницу и заменяю ее старой страницей.

Чтобы имитировать загрузку больших объемов данных из базы данных в буфер, я добавил эту строку: System.Многопоточность.Нитки.Режим ожидания (3000);
в результате, при обновлении самого буфера сетка замораживается на этот период времени.

Теперь, как я понимаю, добавление потока здесь не поможет, потому что есть только 1 операция, которая выполняется только тогда, когда прокрутка достигает небуферизованной строки.

   private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
    {

       e.Value = theBuffer.ReturnFromBuffer(e.RowIndex, e.ColumnIndex);
//I check there if row index is in buffer and if not i update the buffer with new page and return the value

    }
  

Итак, я подумываю о добавлении анимации «вращающегося колеса» над поясом, когда это произойдет.

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

 if (ProgressEvent != null)
     ProgressEvent(true);

System.Threading.Thread.Sleep(3000);

if (ProgressEvent != null)
     ProgressEvent(false);
  

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

Som Я предполагаю, что протектор все еще необходим. Но не могу понять, когда он был запущен и что он должен делать?

Обновить:

Я устал от предложенного фонового рабочего:
Внутри метода, который вызывается событием CellValueNeeded, я проверяю, буферизована ли запрошенная строка, если нет, я запускаю backgroundworker.

  if (!bgWorker.IsBusy) //otherwise worker will be started for every column of the row
 {
      bgWorker.DoWork  = (sender, e) =>
         {
           UpdateBuffer(rowIndex);                           
          };
       bgWorker.RunWorkerAsync();
 }
 return "null"; 
  

И это:

 bgWorker.RunWorkerCompleted  = (sender, e) =>
{
    if (IvalidateEvent != null)
        IvalidateEvent();  //send event to form, where invalidate the gridview  dataGridView1.Invalidate();
};            
  

это работает, но с этим связано несколько проблем:
1) Я получил много фиктивных результатов в своей сетке, даже если это ненадолго — мне это не очень нравится.
2) это даже займет больше времени, если я просто оставлю все как есть, вероятно, потому, что backgroundworker отнимает некоторое время и потому, что я фактически аннулирую grid и вызываю событие CellValueNeeded 2 раза (первый раз для запуска backgroundworker, второй раз, когда он заканчивает свою работу)!

Комментарии:

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

2. @DarkSquirrel42, ячейка нуждается в буферизации значений не только для нужной строки, но и для множества строк впереди. единственная проблема, когда мне нужно загрузить новые строки. Я не против заморозить сетку, но анимация должна появляться по мере того, как это происходит. без фиктивных значений

3. когда в datagridview отображается значение cellvalueneeded, вы можете указать значение, которое будет отображаться в ячейке. если у вас нет этого значения, вы не можете его указать. итак, у вас есть 2 варианта: 1) либо предоставить сообщение «loading», либо ничего (пустая строка), заполнить ваш буфер в асинхронной операции и, когда данные будут готовы, обновить ячейку. или вариант 2) синхронно заполнить буфер и вернуть желаемое значение. конечно, вариант 2 заблокирует поток пользовательского интерфейса, поэтому анимация загрузки, перерисовка формы или взаимодействие с пользователем будут недоступны (если вы не делаете плохие вещи, такие как Application. DoEvents — отлаживать это будет непросто)

4. проблема в том, что когда я, например, нажимаю на pagedown много раз, поэтому фиктивное значение заполняет много строк (пока данные не будут загружены). для тестирования я использую dataset в качестве источника и datatable, который хранит страницу в памяти. Может быть, другие ресурсы будут работать быстрее?

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

Ответ №1:

когда вы достигнете небуферизованной строки, вызовите свой код, чтобы получить требуемые данные во втором потоке (новый поток, threadpool, backgroundworker … выберите один)

после запуска этой асинхронной операции сделайте видимым элемент управления «загрузка …»

(здесь вы, вероятно, захотите отключить части вашего пользовательского интерфейса до завершения операции)

на данный момент верните пустую строку или какое-либо другое фиктивное значение…

ваша асинхронная операция должна получить требуемые данные, а затем вызвать другой метод — поток пользовательского интерфейса (вызовите Invoke(…) для объекта вашей формы)

вызов invoke необходим, чтобы избежать межпоточного взаимодействия с пользовательским интерфейсом

в вызываемом методе вы должны использовать извлеченные данные для заполнения вашего буфера, скрыть элемент управления «загрузка …», сделать недействительными соответствующие строки или ячейки (у datagridview есть методы для этого) и, наконец, что не менее важно, повторно включить ваш пользовательский интерфейс, если необходимо

Комментарии:

1. да, я думал об этом, но есть проблема, которую я попытаюсь объяснить: может возникнуть ситуация, когда новый поток загружает данные в буфер, пользователь может снова прокрутить сетку, и начнется новый поток!! это вызовет много беспорядка. Я не думаю, что хочу блокировать прокрутку.

2. А также, CellValueNeeded ожидает своего значения, как он может использовать поток? он не будет ждать завершения потока

3. 1-й: ожидающий обработчик CellValueNeeded … не позволяйте ему ждать… возвращает фиктивное значение, например. «загрузка …». и позже, когда асинхронная операция завершится и данные окажутся в вашем буфере, сделайте строку / ячейку недействительной. это снова вызовет CellValueNeeded, и тогда вы сможете получить реальное значение. 2-е: чтобы иметь только одного работника одновременно, вы могли бы написать класс, который обрабатывает запросы на данные. когда данные запрашиваются, а в данный момент данные не извлекаются, запустите поток. когда данные в данный момент извлечены, поставьте запрос в очередь… в конце процесса выборки проверьте, нет ли ожидающих запросов…

4. @DarkSquirrel42, что ты подразумеваешь под «аннулированием строки / ячейки» . Также я не думаю, что смогу вернуть фиктивное значение, потому что это поместит его внутрь ячейки.

5. когда элемент управления (например, DataGridViewCell) необходимо перерисовать, вы можете сделать недействительной его клиентскую область, поэтому в следующий раз, когда поток пользовательского интерфейса перейдет в состояние ожидания, он перерисует эту область. обычно вы вызываете Invalidate() для элемента управления, чтобы вызвать это. для datagridview-ячейки / -строки это делается из datagridview-объекта (если я правильно помню, метод называется InvalidateRow(…) / InvalidateCell(…)) в виртуальном режиме datagridview вызовет значение CellValueNeeded, прежде чем сможет перерисовать ячейку. таким образом, у вас есть это фиктивное значение в этой ячейке, пока данные извлекаются в фоновом режиме, а затем появляются реальные данные