Использование BackgroundWorker для обновления пользовательского интерфейса без зависаний?

У меня есть следующий код для заполнения ListView из фонового потока (DoWork вызывает метод PopulateThread):

delegate void PopulateThreadCallBack(DoWorkEventArgs e);
private void PopulateThread(DoWorkEventArgs e)
{

    if (this.InvokeRequired)
    {
        PopulateThreadCallBack d = new PopulateThreadCallBack(this.PopulateThread);
        this.Invoke(d, new object[] { e });
    }
    else
    {

        // Ensure there is some data
        if (this.DataCollection == null)
        {
            return;
        }

        this.Hide();

        // Filter the collection based on the filters
        List<ServiceCallEntity> resultCollection = this.ApplyFilter();

        // Get the current Ids
        List<Guid> previousIdList = this.GetUniqueIdList(listView);
        List<Guid> usedIdList = new List<Guid>();

        foreach (ServiceCallEntity record in resultCollection)
        {

            if (e.Cancel)
            {
                this.Show();
                return;
            }
            else
            {

                // Get the top level entities
                UserEntity userEntity = IvdSession.Instance.Collection.GetEngineerEntity(record.UserId);
                AssetEntity assetEntity = IvdSession.Instance.Collection.GetAssetEntity(record.AssetId);
                SiteEntity siteEntity = IvdSession.Instance.Collection.GetSiteEntity(record.SiteId);
                FaultEntity faultEntity = IvdSession.Instance.Collection.GetFaultEntity(record.FaultId);

                if (siteEntity == null || userEntity == null || faultEntity == null)
                {
                    continue;
                }
                else
                {

                    // Get the linked entities
                    RegionEntity regionEntity = IvdSession.Instance.Collection.GetRegionEntity(siteEntity.RegionId);
                    StatusEntity statusEntity = IvdSession.Instance.Collection.GetStatusEntity(record.ServiceCallStatus.StatusId);

                    ListViewItem item = new ListViewItem(siteEntity.SiteName);
                    item.SubItems.Add(siteEntity.Address);
                    item.Tag = record;

                    item.SubItems.Add(regionEntity.Description);

                    // Handle if an Asset is involved
                    if (record.AssetId > 0)
                        item.SubItems.Add(assetEntity.AssetDisplay);
                    else
                        item.SubItems.Add("N/A");

                    item.SubItems.Add(faultEntity.Description);
                    item.SubItems.Add(userEntity.UserDisplay);

                    item.SubItems.Add("TODO: Claimed By");
                    item.SubItems.Add(record.DateTimeStamp.ToString());

                    IvdColourHelper.SetListViewItemColour(item, false);
                    this.PopulateItem(item, ref usedIdList);

                }

            }

        }

        // Clean up the grid
        this.CleanListView(previousIdList, usedIdList);

        // Only autosize when allowed and when there are some items in the ListView
        if (this.AllowAutoSize && listView.Items.Count > 0)
        {
            rsListView.AutoSizeColumns(listView);
            this.AllowAutoSize = false;
        }

        this.Show();

    }

}

К сожалению, это приводит к зависанию пользовательского интерфейса в foreach... есть ли способ обновить/заполнить ListView, не замораживая основной пользовательский интерфейс?


person djdd87    schedule 12.06.2009    source источник
comment
Просто добавлю - это совершенно неправильный способ сделать то, чего я пытался достичь. С тех пор этот код был удален и исправлен. Принятый ответ - уловка.   -  person djdd87    schedule 19.01.2010


Ответы (3)


A) Вам, вероятно, не нужно использовать this.Invoke, а вместо этого используйте this.BeginInvoke. Invoke блокирует текущий поток.

Б) Вам не нужно определять своих собственных делегатов, вы можете использовать MethodInvoker

if(this.InvokeRequired) {
  this.BeginInvoke(new MethodInvoker(() => PopulateThread(e)));
  return;
}

Это намного чище :)

person Rich Schuler    schedule 12.06.2009
comment
Спасибо, внесу некоторые улучшения. - person djdd87; 12.06.2009

Вы используете Control.Invoke для выполнения почти всего, что означает, что этот код вообще не является многопоточным.

Правильным способом (с участием Backgroundworker) было бы использование события UpdateProgress для добавления элементов. Он уже синхронизирован.

Но поскольку вы скрываете элемент управления (или это форма?) во время этого процесса, вы также можете создать список и по завершении добавить его в список. Этот фрагмент кода не должен занять много времени.

Или какая-то комбинация, добавление небольших списков в событие обновления. И я задаюсь вопросом о мудрости Hide/Show, я ожидаю, что это просто заставит пользовательский интерфейс мерцать. Оставьте их или замените на SuspendLayout/Resumelayout.

person Henk Holterman    schedule 12.06.2009
comment
Следует также добавить, что если у вас есть SortComparer, назначенный списку, добавление/удаление НАМНОГО медленнее. При внесении изменений рекомендуется временно отключить сортировку, а затем снова включить ее после завершения всех добавлений и удалений. - person Jeff Yates; 12.06.2009

Прокачайте события вручную с помощью

Application.DoEvents(); 
person Jared Updike    schedule 12.06.2009
comment
-1, простите, а BgWorker-›Invoke-›DoEvents? Похоже на затыкание одной дыры другой. - person Henk Holterman; 12.06.2009
comment
Хороший момент, хотя я скажу: вы можете продвинуться довольно далеко с Application.DoEvents вместо нескольких потоков, если все, что вам нужно, это умеренно отзывчивый графический интерфейс во время задач с интенсивными вычислениями, просто накачайте очередь сообщений самостоятельно. Многие из этих механизмов — просто дополнительная сложность с небольшим выигрышем (если, конечно, вам действительно не нужны потоки). Например, с несколькими потоками отладка + исключения = FAIL. Но, как вы сказали в своем ответе, это вовсе не многопоточный код. Параллелизм в императивных языках — это PITA. Держитесь подальше, если вам это действительно не нужно. - person Jared Updike; 13.06.2009
comment
Джаред, если ты имел в виду DoEvents вместо BgWorker, то я пропустил это. Однако DoEvents сложна. - person Henk Holterman; 13.06.2009