Я следовал приведенному здесь примеру Как: реализовать виртуальный режим с своевременной загрузкой данных в элементе управления Windows Forms DataGridView для реализации своевременной загрузки для виртуального режима DataGridView
.Это работало хорошо, но учитывая размер базы данных, я заметил блокировку потока пользовательского интерфейса во время звонков на мой IDataPageRetriever
.Чтобы решить эту проблему, я внедрил шаблон async-await
в класс для IDataPageRetriever
.Однако теперь есть много строк, не отображающих никаких значений, или мне нужно щелкнуть их, чтобы они отобразили значение.В сочетании виртуального режима DataGridView
с async-await
должно быть что-то непонятное.
Я предполагаю, что вокруг есть типичный шаблон, и мне не хватает чего-то простого.
Спасибо за ваш вклад!
Редактировать 1: добавление кода
DataGridView CellValueNeeded
private async void dgvCompound_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
var dgv = (DataGridView)sender;
try
{
e.Value = await memoryCache.RetrieveElement(e.RowIndex, dgv.Columns[e.ColumnIndex].DataPropertyName);
}
catch (OperationCanceledException)
{
}
dgv.InvalidateRow(e.RowIndex);
}
Кэш
public class Cache
{
private static int RowsPerPage;
public event EventHandler Initialised;
public event EventHandler CacheChanged;
// Represents one page of data.
public struct DataPage
{
public CompoundDataTable table;
public DataPage(CompoundDataTable table, int rowIndex)
{
this.table = table;
LowestIndex = MapToLowerBoundary(rowIndex);
HighestIndex = MapToUpperBoundary(rowIndex);
System.Diagnostics.Debug.Assert(LowestIndex >= 0);
System.Diagnostics.Debug.Assert(HighestIndex >= 0);
}
public int LowestIndex { get; private set; }
public int HighestIndex { get; private set; }
public static int MapToLowerBoundary(int rowIndex)
{
// Return the lowest index of a page containing the given index.
return (rowIndex / RowsPerPage) * RowsPerPage;
}
private static int MapToUpperBoundary(int rowIndex)
{
// Return the highest index of a page containing the given index.
return MapToLowerBoundary(rowIndex) + RowsPerPage - 1;
}
}
private DataPage[] cachePages;
private IDataPageRetriever dataSupply;
public Cache(IDataPageRetriever dataSupplier, int rowsPerPage)
{
dataSupply = dataSupplier;
Cache.RowsPerPage = rowsPerPage;
LoadFirstTwoPages();
}
public System.Data.SqlClient.SortOrder sortOrder
{
get { return dataSupply.sortOrder; }
set { dataSupply.sortOrder = value; }
}
public string sortByColumn
{
get { return dataSupply.sortByColumn; }
set
{
dataSupply.sortByColumn = value;
Reload();
}
}
public Dictionary<int, float> sortBySimilaritySeachResult
{
get { return dataSupply.sortBySimilaritySeachResult; }
set
{
dataSupply.sortBySimilaritySeachResult = value;
Reload();
}
}
// Sets the value of the element parameter if the value is in the cache.
private bool IfPageCached_ThenSetElement(int rowIndex, int columnIndex, ref string element)
{
if (IsRowCachedInPage(0, rowIndex))
{
if (cachePages[0].table == null || cachePages[0].table.Rows.Count == 0)
{
return true;
}
try
{
element = cachePages[0].table.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
}
catch (Exception exx)
{
throw;
}
return true;
}
else if (IsRowCachedInPage(1, rowIndex))
{
if (cachePages[1].table == null || cachePages[1].table.Rows.Count == 0)
{
return true;
}
try
{
element = cachePages[1].table.Rows[rowIndex % RowsPerPage][columnIndex].ToString();
}
catch (Exception exx)
{
throw;
}
return true;
}
return false;
}
public async Task<string> RetrieveElement(int rowIndex, int columnIndex)
{
string element = null;
if (IfPageCached_ThenSetElement(rowIndex, columnIndex, ref element))
{
return element;
}
else
{
return await RetrieveData_CacheIt_ThenReturnElement(rowIndex, columnIndex);
}
}
static readonly CompoundDataTable c = new CompoundDataTable();
public async Task<string> RetrieveElement(int rowIndex, string colName) => await RetrieveElement(rowIndex, c.Columns[colName].Ordinal);
private async void LoadFirstTwoPages()
{
cachePages = new DataPage[]{
new DataPage(await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(0), RowsPerPage), 0),
new DataPage(await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(RowsPerPage),RowsPerPage), RowsPerPage)
};
Initialised?.Invoke(this, EventArgs.Empty);
CacheChanged?.Invoke(this, EventArgs.Empty);
}
public async void Reload()
{
cachePages[0].table = await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(0), RowsPerPage);
cachePages[1].table = await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(RowsPerPage), RowsPerPage);
CacheChanged?.Invoke(this, EventArgs.Empty);
}
private async Task<string> RetrieveData_CacheIt_ThenReturnElement(int rowIndex, int columnIndex)
{
var IndexToUnusedPage = GetIndexToUnusedPage(rowIndex);
// Retrieve a page worth of data containing the requested value.
try
{
CompoundDataTable table = await dataSupply.SupplyPageOfData(DataPage.MapToLowerBoundary(rowIndex), RowsPerPage);
// Replace the cached page furthest from the requested cell
// with a new page containing the newly retrieved data.
cachePages[IndexToUnusedPage] = new DataPage(table, rowIndex);
return await RetrieveElement(rowIndex, columnIndex);
}
catch (OperationCanceledException)
{
cachePages[IndexToUnusedPage] = new DataPage(null, rowIndex);
throw;
}
}
// Returns the index of the cached page most distant from the given index
// and therefore least likely to be reused.
private int GetIndexToUnusedPage(int rowIndex)
{
if (rowIndex > cachePages[0].HighestIndex && rowIndex > cachePages[1].HighestIndex)
{
int offsetFromPage0 = rowIndex - cachePages[0].HighestIndex;
int offsetFromPage1 = rowIndex - cachePages[1].HighestIndex;
if (offsetFromPage0 < offsetFromPage1)
{
return 1;
}
return 0;
}
else
{
int offsetFromPage0 = cachePages[0].LowestIndex - rowIndex;
int offsetFromPage1 = cachePages[1].LowestIndex - rowIndex;
if (offsetFromPage0 < offsetFromPage1)
{
return 1;
}
return 0;
}
}
// Returns a value indicating whether the given row index is contained
// in the given DataPage.
private bool IsRowCachedInPage(int pageNumber, int rowIndex)
{
return rowIndex <= cachePages[pageNumber].HighestIndex &&
rowIndex >= cachePages[pageNumber].LowestIndex;
}
}
DataRetriver
public class DataRetriever : IDataPageRetriever
{
private SemaphoreSlim _throttle;
private static Queue<CancellationTokenSource> _tasklist;
public DataRetriever()
{
sortByColumn = "Id";
_throttle = new SemaphoreSlim(2);
_tasklist = new Queue<CancellationTokenSource>();
//just add two cancelation dummies
for (int i = 0; i < _throttle.CurrentCount; i++)
{
_tasklist.Enqueue(new CancellationTokenSource());
}
}
public int RowCount
{
get { return DB.dsTgxChemTableAdapters.CompoundTableAdapter.RowCount(); }
}
// Declare variables to be reused by the SupplyPageOfData method.
private string _sortByColumn;
public string sortByColumn
{
get { return _sortByColumn; }
set
{
if (_sortByColumn == value)
{
sortOrder = sortOrder == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
}
else
{
_sortByColumn = value;
sortOrder = SortOrder.Ascending;
}
}
}
public SortOrder sortOrder { get; set; }
List<int> exclusion = new List<int>();
public async Task<CompoundDataTable> SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
CompoundDataTable dt = new CompoundDataTable();
bool dowork = false;
lock (exclusion)
{
if (!exclusion.Contains(lowerPageBoundary))
{
exclusion.Add(lowerPageBoundary);
dowork = true;
}
}
if (dowork)
{
CancellationTokenSource cts = new CancellationTokenSource();
_tasklist.Enqueue(cts);
CancellationTokenSource prevous = _tasklist.Dequeue();
prevous.Cancel();
prevous.Dispose();
await _throttle.WaitAsync(cts.Token);
try
{
if (!cts.IsCancellationRequested)
{
await DB.dsTgxChemTableAdapters.CompoundTableAdapter.FillAsync(dt, lowerPageBoundary, rowsPerPage, sortByColumn, sortOrder, cts.Token);
}
}
finally
{
_throttle.Release();
lock (exclusion)
{
exclusion.Remove(lowerPageBoundary);
}
}
}
return dt;
}
}