在阻塞 UI 线程时使用 WebBrowser 控件加载 html

Load html with WebBrowser Control while Blocking UI Thread

我有一个 .Net 4.5 WinForms 应用程序,其中 html 存储在数据库中。向用户显示页面列表。当他们单击一行时,页面将加载到 WebBrowser 中。我希望加载页面的过程与主 UI 线程同步(IOW,我不希望用户在加载页面之前能够执行任何其他操作)。加载 html 的代码是:

public class DocLoader
{
    private readonly WebBrowser browser;
    readonly TaskCompletionSource<object> loadedTCS = new TaskCompletionSource<object>();

    public DocLoader(WebBrowser browser)
    {
        this.browser = browser;
    }

    private void LoadedComplete(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        Debug.WriteLine("doc completed");
        loadedTCS.TrySetResult(null);
        browser.DocumentCompleted -= LoadedComplete;
    }

    public Task LoadDoc(string html)
    {
        browser.DocumentCompleted += LoadedComplete;
        browser.DocumentText = html;
        return loadedTCS.Task;
    }
}

像这样调用代码:

await new DocLoader(webBrowser1).LoadDoc(html);

将导致消息循环能够处理消息。这是不希望的。例如,用户可以在前一个文档完成加载之前单击列表中的另一个文档。

像这样调用代码:

new DocLoader(webBrowser1).LoadDoc(html).Wait();

导致 UI 冻结并且文档永远不会加载,大概是因为 DocumentCompleted 事件不会触发 w/o 消息循环 运行。

有没有办法完成这个任务并使进程与 UI 线程保持同步?

WebBrowser 需要其父线程发送消息才能正常运行。在你的例子中,它的父线程 主线程 UI,所以你不能用 Wait 阻塞它。一种 hack 可能是禁用整个 UI,因为 browser.DocumentText 异步加载相当快:

Cursor.Current = Cursors.WaitCursor;
mainFrom.Enabled = false;
try
{
    await new DocLoader(webBrowser1).LoadDoc(html);
}
finally
{
    mainFrom.Enabled = true;
    Cursor.Current = Cursors.Default;
}

然而,这对用户来说非常不友好。 正确的方法 是在您的 WebBrowser 异步加载场景中支持取消。如果用户单击另一行(而前一行更新仍在等待中),您需要取消等待中的操作,然后开始新的操作。