새 스레드의 WebBrowser 컨트롤


84

"클릭"할 Uri 목록이 있습니다.이를 위해 Uri별로 새 웹 브라우저 컨트롤을 만들려고합니다. 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);
}

답변:


152

메시지 루프를 펌핑하는 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
    }
}

8
예! System.Windows.Forms를 추가하기 만하면됩니다. 내 하루도 구했습니다. 감사합니다

4
이 코드를 내 상황에 맞게 조정하려고합니다. 나는 WebBrowser객체를 살아있는 상태 로 유지하고 (상태 / 쿠키 등을 저장하기 위해) Navigate()시간이 지남에 따라 여러 호출을 수행해야 합니다. 그러나 Application.Run()더 이상 코드가 실행되는 것을 차단하기 때문에 어디에서 전화를 걸어야할지 모르겠습니다 . 단서가 있습니까?
dotNET 2014

반환 Application.Exit();을 위해 전화 할 수 있습니다 Application.Run().
Mike de Klerk 2014

26

다음은 WebBrowser자동화 와 같은 비동기 작업을 실행하기 위해 비 UI 스레드에서 메시지 루프를 구성하는 방법 입니다. async/await편리한 선형 코드 흐름을 제공하는 데 사용 하고 웹 페이지 집합을 루프로로드합니다. 코드는 이 우수한 게시물을 부분적으로 기반으로하는 즉시 실행 가능한 콘솔 앱입니다 .

관련 답변 :

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

1
훌륭하고 유익한 답변에 감사드립니다! 내가 찾던 바로 그것입니다. 그러나 (의도적으로?) Dispose () 문을 잘못 배치 한 것 같습니다.
wodzu

@ Paweł, 당신 말이 맞아요, 그 코드는 컴파일도 안 했어요 :) 잘못된 버전을 붙여 넣은 것 같아요. 이것을 발견해 주셔서 감사합니다. 좀 더 일반적인 접근 방식을 확인하고 싶을 수 있습니다 : stackoverflow.com/a/22262976/1768303
noseratio

이 코드를 실행하려고했지만 task.Wait();. 내가 뭔가 잘못하고 있니?
0014

1
안녕하세요, 아마도 당신이 이것으로 저를 도울 수있을 것입니다 : stackoverflow.com/questions/41533997/…- 메서드는 잘 작동하지만 Form이 MessageLoopWorker보다 먼저 인스턴스화되면 작동을 멈 춥니 다.
Alex Netkachov

3

과거의 경험으로 볼 때 웹 브라우저는 기본 응용 프로그램 스레드 외부에서 작동하는 것을 좋아하지 않습니다.

대신 httpwebrequests를 사용해보십시오. 비동기로 설정하고 응답이 성공할 때를 알 수 있도록 처리기를 만들 수 있습니다.

사용 방법 -httpwebrequest-net-asynchronously


내 문제는 이것입니다. 클릭 된 Uri를 사용하려면 사이트에 로그인해야합니다. WebRequest로는이 작업을 수행 할 수 없습니다. WebBrowser를 사용하여 이미 IE 캐시를 사용하므로 사이트가 로그인되었습니다. 그 방법이 있습니까? 링크에는 페이스 북이 포함됩니다. 그래서 페이스 북에 로그인해서 webwrequest 링크를 클릭해도 되나요?
Art W

@ArtW 나는이 오래된 코멘트 알지만, 사람들은 아마 설정에 의해 그것을 해결할 수webRequest.Credentials = CredentialsCache.DefaultCredentials;
vapcguy

@vapcguy API라면 예,하지만 로그인을위한 HTML 요소가있는 웹 사이트라면 IE 쿠키 나 캐시를 사용해야합니다. 그렇지 않으면 클라이언트가 Credentials개체 속성으로 무엇을해야 하고 어떻게 채우는 지 모릅니다 HTML.
ColinM

@ColinM이 전체 페이지에서 말하는 컨텍스트는 JavaScript / AJAX에서 할 수있는 것처럼 게시되는 간단한 HTML 및 양식 요소가 아닌 HttpWebRequest 개체와 C # .NET을 사용하는 것입니다. 그러나 어쨌든 당신은 수신기를 가지고 있습니다. 그리고 로그온을 위해서는 Windows 인증을 사용해야하며 IIS는이를 자동으로 처리합니다. 수동으로 테스트해야하는 경우 WindowsIdentity.GetCurrent().Name가장을 구현 한 후 사용 하고 원하는 경우 AD 검색에 대해 테스트 할 수 있습니다 . 쿠키와 캐시가 어떻게 사용되는지 확실하지 않습니다.
vapcguy

@vapcguy 질문은 WebBrowserHTML 페이지가로드되고 있음을 나타내는 것입니다. OP는 심지어 WebRequest그가 원하는 것을 달성하지 못할 것이라고 말했습니다 . 따라서 웹 사이트가 로그인을 위해 HTML 입력을 예상하면 Credentials개체 설정이 작동하지 않습니다. 또한 OP가 말했듯이 사이트에는 Facebook이 포함됩니다. Windows 인증이 작동하지 않습니다.
ColinM

0

여러 WebBrowser가 동시에 작동하는 간단한 솔루션

  1. 새 Windows Forms 응용 프로그램 만들기
  2. button1이라는 버튼 배치
  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("/programming/4269800/webbrowser-control-in-a-new-thread");
        }
        ));
    }
    
    while (completed_count != count)
    {
        Application.DoEvents();
        Thread.Sleep(10);
    }
    textBox1.AppendText("All completed" + Environment.NewLine);
    
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.