Search code examples
c#winformstaskfreezetaskfactory

Task freezes the GUI


I am trying to make create a function to get the source code from a number of pages. After each page is grabbed, I want to update a label on my form indicating the progress (1 of 5, 2 of 5, etc.).

However, no matter what I try, the GUI completely freezes until the for loop has ended.

public List<List<string>> GetPages(string base_url, int num_pages)
{
    var pages = new List<List<string>>();
    var webGet = new HtmlWeb();
    var task = Task.Factory.StartNew(() => {
        for (int i = 0; i <= num_pages; i++)
        {
            UpdateMessage("Fetching page " + i + " of " + num_pages + ".");
            var page = new List<string>();
            var page_source = webGet.Load(url+i);
            // (...)
            page.Add(url+i);
            page.Add(source);
            pages.Add(page);
        }
    });
    task.Wait();
    return pages;
}

The call to this method looks like this:

List<List<string>> pages = site.GetPages(url, num_pages);

If I remove task.Wait(); the GUI unfreezes, the label updates properly, but the code continues without the needed multidimensional list.

I should say that I'm very new to C#. What the heck am I doing wrong?

Update

As per Darin, I have changed my method:

public async Task<List<List<string>>> GetPages(string url, int num_pages)
{
    var pages = new List<List<string>>();
    var webGet = new HtmlWeb();
    for (int i = 0; i <= num_pages; i++)
    {
        UpdateMessage("Fetching page " + i + " of " + num_pages + ".");
        var page = new List<string>();
        var page_source = webGet.Load(url+i);
        // (...)
        page.Add(url+i);
        page.Add(source);
        pages.Add(page);
    }
    return pages;
}

And the call:

List<List<string>> pages = await site.GetPages(url, num_pages);

However, now I am getting this error:

The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.

But when I mark the method with async, the GUI still freezes.

Update 2

Woops! I seem to missed a piece of Darin's new method. I have now included await webGet.LoadAsync(url + i); in the method. I have also marked the method I am calling from as async.

Now, unfortunately, I'm getting this error:

'HtmlWeb' does not contain a definition for 'LoadAsync' and no extension method 'LoadAsync' accepting a first argument of type 'HtmlWeb' could be found (are you missing a using directive or an assembly reference?)

I've checked, I'm using .NET 4.5.2, and the HtmlAgilityPack in my References is the Net45 version. I have no idea what's going on now.


Solution

  • Assuming WinForms, start by making the toplevel eventhandler async void .

    You then have an async method that can await a Task<List<List<string>>> method. That method does not have to be async itself.

    private async void Button1_Click(...)  
    { 
       var pages = await GetPages(...); 
       // update the UI here
    }
    
    
    public Task<List<List<string>>> GetPages(string url, int num_pages)
    {
        ...
        return task;
    }