【问题标题】:WebBrowser Control in a new thread新线程中的 WebBrowser 控件
【发布时间】:2010-11-24 17:41:13
【问题描述】:

我有一个我想要“单击”的列表 Uri 为了实现这一点,我正在尝试为每个 Uri 创建一个新的 Web 浏览器控件。我为每个 Uri 创建一个新线程。我遇到的问题是线程结束在文档完全加载之前,所以我永远无法使用 DocumentComplete 事件。我该如何克服这个问题?

var item = new ParameterizedThreadStart(ClicIt.Click); 
var thread = new Thread(item) {Name = "ClickThread"}; 
thread.Start(uriItem);

public static void Click(object o)
{
    var url = ((UriItem)o);
    Console.WriteLine(@"Clicking: " + url.Link);
    var clicker = new WebBrowser { ScriptErrorsSuppressed = true };
    clicker.DocumentCompleted += BrowseComplete;
    if (String.IsNullOrEmpty(url.Link)) return;
    if (url.Link.Equals("about:blank")) return;
    if (!url.Link.StartsWith("http://") && !url.Link.StartsWith("https://"))
        url.Link = "http://" + url.Link;
    clicker.Navigate(url.Link);
}

【问题讨论】:

    标签: c# multithreading browser


    【解决方案1】:

    您必须创建一个抽出消息循环的 STA 线程。对于像 WebBrowser 这样的 ActiveX 组件来说,这是唯一的好客环境。否则,您将不会收到 DocumentCompleted 事件。一些示例代码:

    private void runBrowserThread(Uri url) {
        var th = new Thread(() => {
            var br = new WebBrowser();
            br.DocumentCompleted += browser_DocumentCompleted;
            br.Navigate(url);
            Application.Run();
        });
        th.SetApartmentState(ApartmentState.STA);
        th.Start();
    }
    
    void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
        var br = sender as WebBrowser;
        if (br.Url == e.Url) {
            Console.WriteLine("Natigated to {0}", e.Url);
            Application.ExitThread();   // Stops the thread
        }
    }
    

    【讨论】:

    • 是的!只需添加 System.Windows.Forms。也拯救了我的一天。谢谢
    • 我正在尝试根据我的情况调整此代码。我必须保持WebBrowser 对象处于活动状态(以保存状态/cookie 等)并随着时间的推移执行多个Navigate() 调用。但我不确定将我的Application.Run() 调用放在哪里,因为它会阻止进一步的代码执行。有什么线索吗?
    • 您可以拨打Application.Exit();Application.Run()返回。
    • 使用任务时如何设置STA?
    【解决方案2】:

    以下是如何在非 UI 线程上组织消息循环,以运行异步任务,如 WebBrowser 自动化。它使用async/await 提供方便的线性代码流并循环加载一组网页。该代码是部分基于this excellent post 的可立即运行的控制台应用程序。

    相关答案:

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace ConsoleApplicationWebBrowser
    {
        // by Noseratio - https://stackoverflow.com/users/1768303/noseratio
        class Program
        {
            // Entry Point of the console app
            static void Main(string[] args)
            {
                try
                {
                    // download each page and dump the content
                    var task = MessageLoopWorker.Run(DoWorkAsync,
                        "http://www.example.com", "http://www.example.net", "http://www.example.org");
                    task.Wait();
                    Console.WriteLine("DoWorkAsync completed.");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("DoWorkAsync failed: " + ex.Message);
                }
    
                Console.WriteLine("Press Enter to exit.");
                Console.ReadLine();
            }
    
            // navigate WebBrowser to the list of urls in a loop
            static async Task<object> DoWorkAsync(object[] args)
            {
                Console.WriteLine("Start working.");
    
                using (var wb = new WebBrowser())
                {
                    wb.ScriptErrorsSuppressed = true;
    
                    TaskCompletionSource<bool> tcs = null;
                    WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) =>
                        tcs.TrySetResult(true);
    
                    // navigate to each URL in the list
                    foreach (var url in args)
                    {
                        tcs = new TaskCompletionSource<bool>();
                        wb.DocumentCompleted += documentCompletedHandler;
                        try
                        {
                            wb.Navigate(url.ToString());
                            // await for DocumentCompleted
                            await tcs.Task;
                        }
                        finally
                        {
                            wb.DocumentCompleted -= documentCompletedHandler;
                        }
                        // the DOM is ready
                        Console.WriteLine(url.ToString());
                        Console.WriteLine(wb.Document.Body.OuterHtml);
                    }
                }
    
                Console.WriteLine("End working.");
                return null;
            }
    
        }
    
        // a helper class to start the message loop and execute an asynchronous task
        public static class MessageLoopWorker
        {
            public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
            {
                var tcs = new TaskCompletionSource<object>();
    
                var thread = new Thread(() =>
                {
                    EventHandler idleHandler = null;
    
                    idleHandler = async (s, e) =>
                    {
                        // handle Application.Idle just once
                        Application.Idle -= idleHandler;
    
                        // return to the message loop
                        await Task.Yield();
    
                        // and continue asynchronously
                        // propogate the result or exception
                        try
                        {
                            var result = await worker(args);
                            tcs.SetResult(result);
                        }
                        catch (Exception ex)
                        {
                            tcs.SetException(ex);
                        }
    
                        // signal to exit the message loop
                        // Application.Run will exit at this point
                        Application.ExitThread();
                    };
    
                    // handle Application.Idle just once
                    // to make sure we're inside the message loop
                    // and SynchronizationContext has been correctly installed
                    Application.Idle += idleHandler;
                    Application.Run();
                });
    
                // set STA model for the new thread
                thread.SetApartmentState(ApartmentState.STA);
    
                // start the thread and await for the task
                thread.Start();
                try
                {
                    return await tcs.Task;
                }
                finally
                {
                    thread.Join();
                }
            }
        }
    }
    

    【讨论】:

    • 感谢您提供的精彩而翔实的答案!这正是我一直在寻找的。但是,您似乎(故意?)放错了 Dispose() 语句。
    • @Paweł,你是对的,该代码甚至没有编译 :) 我认为粘贴了错误的版本,现在已修复。感谢您发现这一点。您可能想要检查更通用的方法:stackoverflow.com/a/22262976/1768303
    • 我试图运行这段代码,但是它卡在了task.Wait();。我做错了什么?
    • 嗨,也许你可以帮我解决这个问题:stackoverflow.com/questions/41533997/… - 该方法运行良好,但如果 Form 在 MessageLoopWorker 之前实例化,它将停止工作。
    【解决方案3】:

    根据我过去的经验,网络浏览器不喜欢在主应用程序线程之外运行。

    尝试使用 httpwebrequests 代替,您可以将它们设置为异步并为响应创建一个处理程序以知道它何时成功:

    how-to-use-httpwebrequest-net-asynchronously

    【讨论】:

    • 我的问题是这个。单击的 Uri 需要登录该站点。我无法使用 WebRequest 实现此目的。通过使用 WebBrowser,它已经使用了 IE 缓存,因此站点已登录。有没有办法解决这个问题?链接涉及facebook。那么我可以登录 facebook 并单击带有 webwrequest 的链接吗?
    • @ArtW 我知道这是一条旧评论,但人们可以通过设置webRequest.Credentials = CredentialsCache.DefaultCredentials; 来解决这个问题
    • @vapcguy 如果是 API 则可以,但如果是带有用于登录的 HTML 元素的网站,则需要使用 IE cookie 或缓存,否则客户端不知道该怎么做使用Credentials 对象属性以及如何填充 HTML。
    • @ColinM 整个页面所讨论的上下文是使用 HttpWebRequest 对象和 C# .NET,而不是像 JavaScript/AJAX 那样发布简单的 HTML 和表单元素。但无论如何,你有一个接收器。对于登录,您应该使用 Windows 身份验证,并且 IIS 无论如何都会自动处理此问题。如果您需要手动测试它们,您可以在实施模拟后使用WindowsIdentity.GetCurrent().Name,如果您愿意,可以针对 AD 搜索对其进行测试。不确定 cookie 和缓存将如何用于其中。
    • @vapcguy 问题是关于WebBrowser,这表明正在加载HTML页面,OP甚至说WebRequest无法实现他想要的,因此如果网站需要HTML登录输入然后设置Credentials 对象将不起作用。此外,正如 OP 所说,这些网站包括 Facebook; Windows 身份验证对此不起作用。
    【解决方案4】:

    一个简单的解决方案,多个浏览器同时运行

    1. 创建一个新的 Windows 窗体应用程序
    2. 放置名为 按钮1
    3. 放置名为 textBox1 的文本框
    4. 设置文本属性 字段:Multiline true 和 ScrollBars Both
    5. 写下 button1 点击处理程序:

      textBox1.Clear();
      textBox1.AppendText(DateTime.Now.ToString() + Environment.NewLine);
      int completed_count = 0;
      int count = 10;
      for (int i = 0; i < count; i++)
      {
          int tmp = i;
          this.BeginInvoke(new Action(() =>
          {
              var wb = new WebBrowser();
              wb.ScriptErrorsSuppressed = true;
              wb.DocumentCompleted += (cur_sender, cur_e) =>
              {
                  var cur_wb = cur_sender as WebBrowser;
                  if (cur_wb.Url == cur_e.Url)
                  {
                      textBox1.AppendText("Task " + tmp + ", navigated to " + cur_e.Url + Environment.NewLine);
                      completed_count++;
                  }
              };
              wb.Navigate("https://stackoverflow.com/questions/4269800/webbrowser-control-in-a-new-thread");
          }
          ));
      }
      
      while (completed_count != count)
      {
          Application.DoEvents();
          Thread.Sleep(10);
      }
      textBox1.AppendText("All completed" + Environment.NewLine);
      

    【讨论】:

      猜你喜欢
      • 2015-01-15
      • 1970-01-01
      • 1970-01-01
      • 2010-11-18
      • 1970-01-01
      • 1970-01-01
      • 2010-12-04
      • 2011-01-20
      • 2017-04-14
      相关资源
      最近更新 更多