Виртуальный режим Datagridview с своевременной загрузкой данных с помощью async-await - PullRequest
0 голосов
/ 10 октября 2018

Я следовал приведенному здесь примеру Как: реализовать виртуальный режим с своевременной загрузкой данных в элементе управления 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;
    }
}

1 Ответ

0 голосов
/ 08 марта 2019

В моем случае я сделал это: я раскрыл несколько вещей на Cache (который я называю «источником данных») - метод LoadNextPage() и событие PageLoaded (это может быть только один асинхронный метод, но я обнаружил, что это разделение привело к немного более чистому коду) и количеству кэшированных строк (в вашем случае это был бы HighestIndex последней кэшированной страницы).

LoadNextPage() запускает процесс асинхронной загрузкиданные, и когда данные загружаются и кэшируются, происходит событие PageLoaded.

Класс пользовательского интерфейса сначала вызывает LoadNextPage() для загрузки первой страницы, затем запускается PageLoaded, и я устанавливаю представление сеткиRowCount к числу загруженных кэшированных строк.

После этого представление сетки начинает вызывать CellValueNeeded для всех ячеек, и вы можете заполнить его синхронно из кэша.Когда требуются данные для последней строки кэша, я снова вызываю LoadNextPage(), и процесс повторяется.

Таким образом, все время обманывается представление сетки, что в нем только кэшированные строки и ничего больше.

Один улов был в том, что CellValueNeeded может вызываться несколько раз для одной и той же строки, поэтому убедитесь, что вы не загружаете вещи дважды в этом случае.

Я сделал это для входа в GitNitroGit Git-клиент для Windows , так что это односторонний процесс загрузки, означающий, что страницы от 1 до N всегда кэшируются.Если у вас другой случай, например, вы хотите начать с середины и прокрутить вверх или даже заполнить случайные страницы, вам нужно проделать больше работы, но возможно с тем же принципом обмана вида сетки, чтобы просто иметь одинаковое числострок в вашем кэше, а затем сопоставление между индексом строки представления сетки и индексом реальных данных, при этом данные заполняются «на стороне», когда границы сетки достигаются в сетке на экране.

...