【问题标题】:Using BackgroundWorker to update the UI without freezes...?使用 BackgroundWorker 更新 UI 而不冻结...?
【发布时间】:2010-11-02 13:29:30
【问题描述】:

我有以下代码用于从后台线程填充 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();

    }

}

不幸的是,这会导致 UI 在 foreach 中冻结...有没有办法更新/填充 ListView 而不会冻结主 UI?

【问题讨论】:

  • 只是补充一下-这是完全错误的方法来做我想要实现的目标。此后,此代码已被删除并修复。接受的答案是一个错误。

标签: c# multithreading user-interface delegates backgroundworker


【解决方案1】:

A) 您可能不需要使用 this.Invoke,而是使用 this.BeginInvoke。 Invoke 阻塞当前线程。

B) 你不需要定义你自己的委托,你可以使用 MethodInvoker

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

干净多了:)

【讨论】:

  • 谢谢,我会改进的。
【解决方案2】:

您正在使用 Control.Invoke 执行几乎所有内容,这意味着此代码根本不是多线程的。

正确的方法(涉及 Backgroundworker)是使用 UpdateProgress 事件来添加元素。它已经同步了。

但是由于您在此过程中隐藏了控件(或者它是 Form ?),您不妨构建一个 List 并在完成后将其添加到 Listview 中。这段代码应该不会花很长时间。

或某种组合,在更新事件中添加小列表。我想知道隐藏/显示的智慧,我希望这只会让 UI 闪烁。将它们排除在外或替换为 SuspendLayout/Resumelayout。

【讨论】:

  • 还应该补充一点,如果你有一个 SortComparer 分配给列表,添加/删除会慢得多。进行修改时,最好暂时禁用排序,然后在所有添加和删除完成后重新启用它。
【解决方案3】:

使用手动泵送事件

Application.DoEvents(); 

【讨论】:

  • -1,对不起,但是 BgWorker->Invoke->DoEvents?看起来像是用另一个洞修补一个洞。
  • 好点,但我会说:如果您想要的只是在计算密集型任务期间响应适中的 GUI,那么您可以使用 Application.DoEvents 而不是多线程取得相当大的进展,只需自己泵送消息队列即可。很多这种机器只是额外的复杂性,几乎没有收获(当然,除非你真的需要线程)。例如,对于多线程,调试 + 异常 = FAIL。但正如您在回答中所说,这根本不是多线程代码。命令式语言中的并发是 PITA。除非你真的需要,否则请远离。
  • Jared,如果你的意思是 DoEvents 而不是 BgWorker,那我就错过了。不过,DoEvents 很棘手。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多