2017-08-29 31 views
0

试图围绕我的头围绕TaskCompletionSource。这里是我写同步的一个小课程(WebBrowser.Navigate()是异步)下载网页并将其返回给调用者。我不确定我是否正确使用了TaskCompletionSource。有人可以指出我在这里错过了什么,或者如果这完全是过度设计的解决方案?WebBrowser控件:创建一个同步包装

class PageDownloader 
{ 
    private WebBrowser _WB = new WebBrowser(); 
    private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); 

    public PageDownloader() 
    { 
    _WB.LoadCompleted += _WB_LoadCompleted; 
    } 

    public string Download(string url) 
    { 
    _WB.Navigate(new Uri(url)); 

    tcs.Task.Wait(); 

    if (tcs.Task.IsCanceled || tcs.Task.IsFaulted) 
     return null; 
    else 
     return (_WB.Document as mshtml.HTMLDocument).body.innerHTML; 
    } 

    private void _WB_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) 
    { 
    var docTemp = _WB.Document as mshtml.HTMLDocument; 
    foreach (mshtml.IHTMLImgElement imgElemt in docTemp.images) 
     imgElemt.src = ""; 

    tcs.SetResult(true); 
    } 
} 
+0

不应该下载方法是异步的,所以你可以真正等待它? – mm8

+0

@ mm8:不。这是整个班级的重点。我正在尝试创建一个“同步”版本。 – dotNET

+0

然后你可以直接处理WebBrowser控件的LoadCompleted事件,不是吗? – mm8

回答

1

我不知道你的意思async这里是什么,但WebBrowser.Navigate方法简单地返回void,不能使用C#5中引入的async/await关键词来等待。它会踢出导航操作并立即返回,如果您想在导航实际完成后执行某些操作,则应订阅LoadCompleted事件处理程序。到现在为止还挺好。

使用TaskCompletionSource<T>你实际上可以使你的async类,所以你其实可以await结果的Download方法。你可能也想赶上你的LoadCompleted事件处理可能发生的任何异常:

class PageDownloader 
{ 
    private WebBrowser _WB = new WebBrowser(); 
    private TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(); 

    public PageDownloader() 
    { 
     _WB.LoadCompleted += _WB_LoadCompleted; 
    } 

    public async Task<string> DownloadAsync(string url) 
    { 
     _WB.Navigate(new Uri(url)); 

     await tcs.Task.ConfigureAwait(false); 

     if (tcs.Task.IsCanceled || tcs.Task.IsFaulted) 
      return null; 
     else 
      return tcs.Task.Result; 
    } 

    private void _WB_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) 
    { 
     try 
     { 
      var docTemp = _WB.Document as mshtml.HTMLDocument; 
      foreach (mshtml.IHTMLImgElement imgElemt in docTemp.images) 
       imgElemt.src = ""; 

      tcs.SetResult(docTemp.body.innerHTML); 
     } 
     catch(Exception ex) 
     { 
      tcs.SetException(ex); 
     } 
    } 
} 

用法:

PageDownloader downloader = new PageDownloader(); 
string html = await downloader.DownloadAsync("http://stackoverflow.com"); 
//or if you want to block synchronously 
string html = downloader.DownloadAsync("http://stackoverflow.com").Result; 

你或许还可以创建在您的类同步超载:

public string Download(string url) 
{ 
    return DownloadAsync(url).Result; 
} 
+0

也许我太快点击了。你的代码碰到'await'行,但从来没有从那里前进。你能给它一个快速测试吗? – dotNET

+0

哪些等待线? SetResult方法是否被调用? – mm8

+0

可能必须将WebBrowser添加到可视化树中以使LoadCompleted事件被触发。因此,您可以使用您在XAML中定义的WebBroswer控件注入类,而不是创建一个从未显示的新类。但是这与TaskCompletionSource无关。 – mm8

0

到目前为止,我一直在使用的唯一解决方案是使用以下代码使用Windows窗体WebBrowser控件:

public string Download(string url) 
{ 
    bool flag = false; 

    using (System.Windows.Forms.WebBrowser WB = new System.Windows.Forms.WebBrowser()) 
    { 
    WB.DocumentCompleted += (sender, e) => 
    { 
     var docTemp = WB.Document; 
     foreach (HtmlElement imgElemt in docTemp.Images) 
     imgElemt.SetAttribute("src", ""); 

     flag = true; 
    }; 

    WB.Navigate(url); 

    while (!flag) 
     Application.DoEvents(); 

    return WB.Document.Body.InnerHtml; 
    } 
} 

即使我只需更换的WinForms版本与WPF WebBrowser控制这个代码,这个代码永远不会打return线。

寻找更好的答案。