virtualmode Datagridview通过异步等待数据检索器实时加载数据

时间:2018-10-10 10:37:06

标签: c# winforms async-await virtualmode

我按照此处How to: Implement Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control给出的示例为虚拟模式func(...interface{})实现了即时加载。这很好用,但考虑到数据库的大小,我注意到在调用DataGridView时UI线程被阻塞。为了解决这个问题,我在IDataPageRetriever的类中实现了async-await模式。但是,现在有很多行不显示任何值,或者我需要单击它们以使其显示值。将虚拟模式IDataPageRetrieverDataGridView组合在一起时,一定存在一些不直接的问题。

我认为周围有一种典型的模式,我想念一些基本的东西。

谢谢您的输入!

编辑1:添加代码

DataGridView的CellValueNeeded

async-await

缓存

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

DataRetriver

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

1 个答案:

答案 0 :(得分:0)

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

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

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

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

因此,一直在欺骗网格视图,使其仅缓存行,而没有其他内容。

一个要注意的是,同一行中的CellValueNeeded可能被多次调用,因此请确保在这种情况下不要并行加载两次。

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