任务冻结 GUI

Task freezes the GUI

我正在尝试创建一个函数以从多个页面获取源代码。抓取每个页面后,我想更新表单上的标签以指示进度(5 个中的 1 个、5 个中的 2 个等)。

但是,无论我尝试什么,GUI 都会完全冻结,直到 for 循环结束。

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;
}

对该方法的调用如下所示:

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

如果我删除 task.Wait();,GUI 会解冻,标签会正确更新,但代码会继续,但没有所需的多维列表。

我应该说我是 C# 的新手。我到底做错了什么?

更新

按照达林的说法,我改变了我的方法:

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;
}

并调用:

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

但是,现在我收到这个错误:

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'.

但是当我用异步标记方法时,GUI 仍然冻结。

更新 2

糟糕!我似乎错过了Darin的新方法。我现在在方法中包含 await webGet.LoadAsync(url + i); 。我还将调用的方法标记为 async.

现在,不幸的是,我收到了这个错误:

'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?)

我查过了,我用的是.NET 4.5.2,我的References里面的HtmlAgilityPack是Net45版本的。我不知道现在发生了什么。

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

这很正常。您应该更新您的函数,使其不再 return 值而是任务:

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

    return task;
}

然后在调用此函数时,您将在结果上使用 ContinueWith:

var task = GetPages(baseUrl, numPages);
task.ContinueWith(t => 
{
    List<List<string>> chapters = t.Result;
    // Do something with the results here
});

显然,在继续访问 t.Result 之前,您可能想先检查其他属性,看看任务是否成功完成,或者是否抛出了一些异常,以便您可以采取相应的行动。

此外,如果您使用的是 .NET 4.5,您可以考虑利用 async/await constructs:

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

然后:

List<List<string>> chapters = await GetPages(baseUrl, numPages);
// Do something with the results here.

假设使用 WinForms,首先制作顶层事件处理程序 async void .

然后你有一个异步方法可以 await 一个 Task<List<List<string>>> 方法。该方法不必是 async 本身。

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;
}