完成事件的异步方法

时间:2013-02-12 15:54:49

标签: c# .net asynchronous

我使用.net 4.0,我试图弄清楚如何使用异步方法来等待DocumentCompleted事件完成并返回值。我的原始代码在上面,如何在这种情况下将其转换为异步/等待模型?

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

2 个答案:

答案 0 :(得分:10)

因此我们需要一个在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();

答案 1 :(得分: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...函数。这样可以确保即使发生边缘案例交错执行时也不会错误地设置结果。