8

我使用.net 4.0,我试图弄清楚如何使用异步方法来等待 DocumentCompleted 事件完成并返回值。我的原始代码在上面,在这种情况下我怎样才能把它变成 async/await 模型?

private class BrowserWindow
    {
        private bool webBrowserReady = false;
        public string content = "";


        public void Navigate(string url)
        {

            xxx browser = new xxx();

            browser.DocumentCompleted += new EventHandler(wb_DocumentCompleted);
            webBrowserReady = false;
            browser.CreateControl();
            if (browser.IsHandleCreated)
                browser.Navigate(url);


            while (!webBrowserReady)
            {
                //Application.DoEvents(); >> replace it with async/await 
            }

        }

        private void wb_DocumentCompleted(object sender, EventArgs e)
        {
            try
            {
               ...

                  webBrowserReady = true;

                  content = browser.Document.Body.InnerHtml;

            }
            catch
            {

            }

        }

        public delegate string AsyncMethodCaller(string url);
    }
4

2 回答 2

12

所以我们需要一个在DocumentCompleted事件触发时返回任务的方法。任何时候你需要一个给定的事件,你可以创建一个这样的方法:

public static Task WhenDocumentCompleted(this WebBrowser browser)
{
    var tcs = new TaskCompletionSource<bool>();
    browser.DocumentCompleted += (s, args) => tcs.SetResult(true);
    return tcs.Task;
}

一旦你有了,你可以使用:

await browser.WhenDocumentCompleted();
于 2013-02-12T16:16:50.733 回答
1

@Servy 有我正在寻找的天才答案,但它不适用于我的用例。TaskCompletionSource由于事件处理程序试图在后续事件调用上设置结果,我在多次引发事件时发现错误。

我从两个方面加强了他的回答。第一个是DocumentCompleted在第一次处理后取消订阅该事件。

public static Task WhenDocumentCompleted(this WebBrowser browser)
{
    var tcs = new TaskCompletionSource<bool>();
    browser.DocumentCompleted += DocumentCompletedHandler;
    return tcs.Task;

    void DocumentCompletedHandler(object sender, EventArgs e)
    {
        browser.DocumentCompleted -= DocumentCompletedHandler;
        tcs.SetResult(true);
    }
}

注意我在这里使用本地函数来捕获TaskCompletionSource实例,这至少需要 C# 7.0。

第二个增强是添加超时。我的特定用例是在单元测试中,我想让等待我的特定事件具有确定性,而不是在出现问题时无限期等待。

我为此选择使用计时器,并将其设置为仅触发一次,然后在不再需要时停止计时器。或者可以利用CancellationToken这里来管理,TaskCompletionSource但我觉得这需要维护者更多地了解它们的用法,并且计时器更容易被普遍理解。

public static Task WhenDocumentCompleted(this WebBrowser browser, int timeoutInMilliseconds = 500)
{
    var tcs = new TaskCompletionSource<bool>();

    var timeoutTimer = new System.Timers.Timer(timeoutInMilliseconds);
    timeoutTimer.AutoReset = false;
    timeoutTimer.Elapsed += (s,e) => tcs.TrySetCanceled();
    timeoutTimer.Start();

    browser.DocumentCompleted += DocumentCompletedHandler;

    return tcs.Task;

    void DocumentCompletedHandler(object sender, EventArgs e)
    {
        timeoutTimer.Stop();
        browser.DocumentCompleted  -= DocumentCompletedHandler;
        tcs.TrySetResult(true);
    }
}

请注意,为了确保代码是线程安全的,我在这里采取了更多的防御措施并使用了Try...函数。这确保即使发生边缘情况交错执行,设置结果也不会出错。

于 2020-12-03T03:44:42.837 回答