I have these two methods, that I want to run async to keep the UI responsive. However, it's still hanging the UI. Any suggestions?
async void DoScrape()
{
var feed = new Feed();
var results = await feed.GetList();
foreach (var itemObject in results)
{
var item = new ListViewItem(itemObject.Title);
item.SubItems.Add(itemObject.Link);
item.SubItems.Add(itemObject.Description);
LstResults.Items.Add(item);
}
}
public class Feed
{
async public Task<List<ItemObject>> GetList()
{
var client = new WebClient();
string content = await client.DownloadStringTaskAsync(new Uri("anyUrl"));
var lstItemObjects = new List<ItemObject>();
var feed = new XmlDocument();
feed.LoadXml(content);
var nodes = feed.GetElementsByTagName("item");
foreach (XmlNode node in nodes)
{
var tmpItemObject = new ItemObject();
var title = node["title"];
if (title != null) tmpItemObject.Title = title.InnerText;
var link = node["link"];
if (link != null) tmpItemObject.Link = link.InnerText;
var description = node["description"];
if (description != null) tmpItemObject.Description = description.InnerText;
lstItemObjects.Add(tmpItemObject);
}
return lstItemObjects;
}
}
You seem to be confusing async with parallel. They are both based on Tasks, but they are completely different. Do not assume that
async
methods run in parallel -- they don't.Async defaults to work in the same thread, unless there are reasons that force the async engine to spin up a new thread, such as the case when the main thread does not have a message pump. But in general, I tend to think of the
async
keyword as running in the same thread.You use WinForms, so the UI thread has a message pump. Therefore, all your code above runs in the UI thread.
You must understand that you have NOT introduced any parallelism here. What you have introduced via the
async
keyword is asynchronous operations, NOT parallel. You have not done anything to "make your UI responsive" except for that one call toDownloadStringTaskAsync
which won't force you to wait for the data to arrive, but you STILL have to do all the network processing (DNS lookup etc.) in the UI thread -- here is the asynchronous operation in play (you essentially "save" the time waiting for downloads).In order to keep UI's responsive, you need to spin off time-consuming work into a separate thread while keeping the UI thread free. You're not doing this with the
async
keyword.You need to use
Task.Factory.StartNew(...)
to explicitly spin up a new thread to do your background processing.How many items are you adding into your list view?
Unless you take action to prevent it, the WinForms list view will do a lot of processing every time you add an item into the list. This can take so long that adding just 100 items can take several seconds.
Try using
BeginUpdate
andEndUpdate
around your loop to defer the bookkeeping of ListView until you're finished.Got to use a try finally to avoid all sorts of pain if there's an exception.
I suspect
DownloadStringTaskAsync
relies uponHttpWebRequest.BeginGetResponse
at a lower level. This being the case, it is known that the setup for a webrequest is not fully asynchronous. Annoyingly (and frankly, stupidly) the DNS lookup phase of an asynchronous WebRequest is performed synchronously, and therefore blocks. I suspect this might be the issue you are observing.Reproduced below is a warning in the docs:
You've two choices:
We went for the 3rd (and costly) option of implementing our own properly asynchronous HTTP library to get decent throughput, but it's probably a bit extreme in your case ;)