I have the following code for population a ListView from a background thread (DoWork calls the PopulateThread method):
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();
}
}
Unfortunately, this causes the UI to freeze whilst in the foreach... is there any way to update/populate the ListView without it freezing the main UI?
Pump the events manually with
A) You probably don't need to use this.Invoke and instead use this.BeginInvoke. Invoke blocks the current thread.
B) You don't need to define your own delegates you can use MethodInvoker
It's much cleaner :)
You are using Control.Invoke to execute just about everything, meaning this code isn't multithreaded at all.
The proper way (involving a Backgroundworker) would be to use the UpdateProgress event to add elements. It is already synchronized.
But since you're hiding the control (or is it the Form ?) during this process you might as well build a List and on completion add it to the Listview. That piece of code shouldn't take long.
Or some sort of combination, adding small lists in an update event. And I wonder about the wisdom of Hide/Show, I expect this to just make the UI flicker. Leave them out or replace with SuspendLayout/Resumelayout.