首页 > 解决方案 > 通过异步等待数据检索器及时加载数据的虚拟模式 Datagridview

问题描述

我按照此处给出的示例How to: Implement Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control来实现虚拟模式的即时加载DataGridView。这很好用,但考虑到数据库的大小,我注意到在调用我的IDataPageRetriever. 为了解决这个问题,我async-awaitIDataPageRetriever. 但是,现在有很多行没有显示任何值,或者我需要单击它们以使它们显示值。将虚拟模式DataGridViewasync-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;
    }
}

数据检索器

    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;
    }
}

标签: c#winformsasync-awaitvirtualmode

解决方案


在我的情况下,我这样做了:我在Cache(我称之为“数据源”)上公开了一些东西 - 一个方法LoadNextPage()和一个事件PageLoaded(这可能只是一个异步方法,但我发现这种拆分导致代码更简洁),以及缓存的行数(在您的情况下,它将是最后一个缓存页面的 HighestIndex)。

LoadNextPage()启动数据的异步加载过程,当数据被加载和缓存时,PageLoaded事件触发。

UI 类首先调用LoadNextPage()以加载第一页,然后PageLoaded触发并将网格视图设置RowCount为加载的缓存行数。

之后,网格视图开始调用CellValueNeeded所有单元格,您可以从缓存中同步填充它。当它需要缓存最后一行的数据时,我LoadNextPage()再次调用,并重复该过程。

所以网格视图一直被欺骗,它只有缓存的行,没有别的。

一个问题是CellValueNeeded可能会为同一行多次调用,因此请确保在这种情况下您没有并行加载两次。

我是为 Windows 的 NitroGit Git 客户端中的 Git 日志做的,所以这是一个单向加载过程,这意味着从 1 到 N 的页面总是被缓存。如果您有不同的情况,例如想要从中间开始向上滚动,甚至填充随机页面,您需要做更多的工作,但可以使用相同的原理来欺骗网格视图以仅具有相同的数字行,然后在网格视图行索引和实际数据索引之间进行映射,同时在屏幕上的网格中达到缓存边界时“在一侧”填充数据。


推荐阅读