【问题标题】:async method with completed event具有完成事件的异步方法
【发布时间】:2013-01-27 23:42:09
【问题描述】:

我使用 .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);
    }

【问题讨论】:

    标签: c# .net asynchronous


    【解决方案1】:

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

    【讨论】:

    • 感谢您的出色解决方案,但一个问题是我使用的是 vs 2010 和 .net 4.0,因此我必须使用旧样式而不是 await synctax (4.5)。我对如何让它在 4.0 中工作感到有点困惑
    • @namvo 你特别说你想要一个使用 async/await 的解决方案,所以我假设你正在使用兼容包。如果不是,那么只需在方法返回的任务上使用ContinueWith(WhenDocumentCompleted 事件将在 4.0 中起作用)并将文档完成后应该运行的代码放在那里。为了方便起见,您很可能希望使用 lambda。
    • 这似乎是一个非常有用且必不可少的知识点,结果却比我预期的更难找到。基本上,这似乎是您定义 aynsc 任务的方式,这些任务不仅仅包含其他异步任务。有没有更普遍的地方可以让人们在学习异步编程时了解这一点?是否还有其他相关的知识(与可以提供任务的 TaskCompletionSource 相关的其他类)?我发现我也应该适当地设置异常,但想知道我是否遗漏了其他任何东西。
    • @BlueMonkMN 有很多关于这个主题的书籍,很多在线教程,很多博客,很多关于 SO 的问题。找到有关该主题的信息一点也不难。
    • @Servy 我想我只是惊讶于它在 Microsoft 异步任务文档 (docs.microsoft.com/en-us/dotnet/csharp/async) 和其他通常谈论异步任务的文章中并不明显。我现在看到谷歌搜索“custom async task c#”比“async taks c#”提供了更多相关的结果。
    【解决方案2】:

    @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... 函数。这确保即使发生边缘情况交错执行,设置结果也不会出错。

    【讨论】:

      猜你喜欢
      • 2012-11-23
      • 1970-01-01
      • 2015-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-14
      相关资源
      最近更新 更多