Избегайте состояния гонки при удалении строки в пользовательском DataGridView
Я знаю, что код ниже C++-CLI, но он относится к C# таким же образом
Когда я хотел "объединить" несколько строк в DataGridView
Я сначала попробовал создать кастомDataGridViewRow
, но это имело тот недостаток, что было практически невозможно реализовать другие типы ячеек, чем DataGridViewTextBoxCell
,
Моим следующим решением было создание кастома DataGridView
, который работает очень хорошо сейчас.
Переопределяет функции
OnCellFormatting ()
OnCellPainting ()
OnRowsRemoved ()
OnSelectionChanged ()
Проблема в том, что часто во время отладки, а иногда и во время нормального выполнения программы, я получаю исключение из разных мест внутреннего кода.net, который вызывается для отрисовки DataGridView или его частей. Пример:
Stack Trace:
bei System.Windows.Forms.DataGridViewTextBoxCell.PaintPrivate(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, Int32 rowIndex, DataGridViewElementStates cellState, Object formattedValue, String errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts, Boolean computeContentBounds, Boolean computeErrorIconBounds, Boolean paint)
bei System.Windows.Forms.DataGridViewTextBoxCell.Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, Int32 rowIndex, DataGridViewElementStates cellState, Object value, Object formattedValue, String errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
bei System.Windows.Forms.DataGridViewCell.PaintWork(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, Int32 rowIndex, DataGridViewElementStates cellState, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
bei System.Windows.Forms.DataGridViewRow.PaintCells(Graphics graphics, Rectangle clipBounds, Rectangle rowBounds, Int32 rowIndex, DataGridViewElementStates rowState, Boolean isFirstDisplayedRow, Boolean isLastVisibleRow, DataGridViewPaintParts paintParts)
bei System.Windows.Forms.DataGridViewRow.Paint(Graphics graphics, Rectangle clipBounds, Rectangle rowBounds, Int32 rowIndex, DataGridViewElementStates rowState, Boolean isFirstDisplayedRow, Boolean isLastVisibleRow)
bei System.Windows.Forms.DataGridView.PaintRows(Graphics g, Rectangle boundingRect, Rectangle clipRect, Boolean singleHorizontalBorderAdded)
bei System.Windows.Forms.DataGridView.PaintGrid(Graphics g, Rectangle gridBounds, Rectangle clipRect, Boolean singleVerticalBorderAdded, Boolean singleHorizontalBorderAdded)
bei System.Windows.Forms.DataGridView.OnPaint(PaintEventArgs e)
bei System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
bei System.Windows.Forms.Control.WmPaint(Message& m)
bei System.Windows.Forms.Control.WndProc(Message& m)
bei System.Windows.Forms.DataGridView.WndProc(Message& m)
bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
bei System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
Это вызвано удалением нескольких строк одновременно из потока, не являющегося пользовательским интерфейсом. На самом деле, удаление их сразу было бы хорошо и решило бы мою проблему, но это невозможно, потому что события вызываются после удаления каждой отдельной строки.:-(
Исключение возникает, когда строка удаляется во время рисования, что, конечно, не может работать. Это случается иногда и не всегда, так что это определенно условие гонки между удалением и просмотром.
Мой вопрос: как я могу избежать этого состояния гонки, поскольку у меня нет доступа к коду, который его вызывает?
Что я сделал до этого: перед началом удаления нескольких строк я установил
m_bUpdateControl = true
Во всех 4 переопределенных функциях у меня есть
if (m_bUpdateControl)
return;
но это еще не решило это.
Что я сделал дальше:
в
OnCellFormatting()
я используюif (m_bUpdateControl) { i_oEventArgs->FormattingApplied = true; return; }
в
OnCellPainting()
я используюif (m_bUpdateControl) { i_oEventArgs->Handled = true; return; }
Но этого все же недостаточно, чтобы безопасно избежать состояния гонки.
Что еще я мог сделать?
Произойдет ли это и на обычном DGV?
Единственная оставшаяся идея - вызвать функцию удаления строк в потоке пользовательского интерфейса, поэтому на DGV будет работать только один поток, и, как мы надеемся, условие гонки исчезнет.
Редактировать:
Я тестировал с обычным DGV и всегда получал InvalidOperationException
при удалении строк из потока, не являющегося пользовательским интерфейсом. Конечно, это имеет смысл, поскольку операции с несколькими потоками над объектами пользовательского интерфейса невозможны. Поэтому я удивился, почему это было возможно в моем приложении.
Я создал небольшой тестовый проект и наконец нашел причину: основное приложение реализует DLL. Как продавец сказал мне в телефонном звонке, процедура Init основного класса этой DLL-звонков
Control.CheckForIllegalCrossThreadCalls = false;
который включает последующие операции с несколькими потоками. Поэтому, если бы эта проверка не была отключена, я бы все равно был вынужден удалить строку в потоке пользовательского интерфейса, что, скорее всего, позволит избежать первоначальной проблемы.
1 ответ
Сочетание
- вызов удаления строки в потоке пользовательского интерфейса
OnCellPainting()
сif (m_bUpdateControl) { i_oEventArgs->Handled = true; return; }
похоже на работу.
В
void OnCellFormatting (...)
{
DataGridView::OnCellFormatting (i_oEventArgs);
if (IsConsecutiveCell (i_oEventArgs->RowIndex, i_oEventArgs->ColumnIndex))
{
i_oEventArgs->Value = String::Empty;
i_oEventArgs->FormattingApplied = true;
}
}
Мне пришлось удалить всю защиту, чтобы DGV не заканчивал ячейками с разными типами контента в одном столбце, что вызывало другое сообщение об ошибке и исключение.
Я все еще хотел бы знать другие решения, если они существуют.