Método de llamada en el hilo correcto cuando el control se crea en un nuevo hilo ()

He creado un nuevo control WebBrowser () en un nuevo Thread () .

El problema que tengo es que cuando invoco a un delegado para mi WebBrowser desde el subproceso principal , la llamada se produce en el subproceso principal . Espero que esto suceda en browserThread .

private static WebBrowser defaultApiClient = null; delegate void DocumentNavigator(string url); private WebApi() { // Create a new thread responsible // for making API calls. Thread browserThread = new Thread(() => { defaultApiClient = new WebBrowser(); // Setup our delegates documentNavigatorDelegate = new DocumentNavigator(defaultApiClient.Navigate); // Anonymous event handler defaultApiClient.DocumentCompleted += (object sender, WebBrowserDocumentCompletedEventArgs e) => { // Do misc. things }; Application.Run(); }); browserThread.SetApartmentState(ApartmentState.STA); browserThread.Start(); } DocumentNavigator documentNavigatorDelegate = null; private void EnsureInitialized() { // This always returns "false" for some reason if (defaultApiClient.InvokeRequired) { // If I jump ahead to this call // and put a break point on System.Windows.Forms.dll!System.Windows.Forms.WebBrowser.Navigate(string urlString, string targetFrameName, byte[] postData, string additionalHeaders) // I find that my call is being done in the "Main Thread".. I would expect this to be done in "browserThread" instead object result = defaultApiClient.Invoke(documentNavigatorDelegate, WebApiUrl); } } 

He intentado invocar el método de miles de maneras:

 // Calls on Main Thread (as expected) defaultApiClient.Navigate(WebApiUrl); // Calls on Main Thread defaultApiClient.Invoke(documentNavigatorDelegate, WebApiUrl); // Calls on Main Thread defaultApiClient.BeginInvoke(documentNavigatorDelegate, WebApiUrl); // Calls on Main Thread documentNavigatorDelegate.Invoke(WebApiUrl); // Calls on random Worker Thread documentNavigatorDelegate.BeginInvoke(WebApiUrl, new AsyncCallback((IAsyncResult result) => { .... }), null); 

Actualizar

Permítanme desglosar un poco mi objective final para aclarar las cosas: tengo que hacer llamadas usando WebBrowser.Document.InvokeScript() , sin embargo, el documento no se carga hasta después de que llame a WebBrowser.Navigate() y ENTONCES al WebBrowser.DocumentComplete evento incendios. Esencialmente, no puedo realizar mi llamada a InvokeScript() hasta después de que se active DocumentComplete … Me gustaría ESPERAR a que se cargue el documento (bloqueando a la persona que llama) para poder llamar a InvokeScript y devolver mi resultado de manera sincrónica.

Básicamente, tengo que esperar a que se complete mi documento y la forma en que me gustaría hacerlo es con una clase AutoResetEvent() que se activará cuando se AutoResetEvent() a DocumentComplete … y necesito que todo esto suceda en un hilo separado.

La otra opción que veo es hacer algo como esto:

 private bool initialized = false; private void EnsureInitialized(){ defaultApiClient.Navigate(WebApiUrl); while(!initialized){ Thread.Sleep(1000); // This blocks so technically wouldn't work } } private void defaultApiClient_DocumentComplete(object sender, WebBrowserDocumentCompletedEventArgs e){ initialized = true; } 

Esto es por diseño. Los miembros InvokeRequired / BeginInvoke / Invoke de un control requieren que se cree la propiedad Handle del control. Esa es la forma principal por la que puede averiguar a qué hilo específico invocar.

Pero eso no sucedió en su código, el Handle normalmente solo se crea cuando agrega un control a la colección de controles de un padre y el padre se mostró con Show (). En otras palabras, en realidad creó la ventana de host para el navegador. Nada de esto sucedió en su código, por lo que Handle sigue siendo IntPtr.Zero e InvokeRequired devuelve false .

Esto no es realmente un problema. La clase WebBrowser es especial, es un servidor COM bajo el capó. COM maneja los detalles de los hilos en lugar de dejarlos en manos del progtwigdor, muy diferente de la forma en que funciona .NET. Y automáticamente hará una llamada a su método Navigate (). Esto es completamente automático y no requiere ninguna ayuda. Todo lo que necesita es un hogar hospitalario para el servidor COM, creó uno creando un subproceso STA y activando un bucle de mensajes con Application.Run (). Es el bucle de mensajes que COM utiliza para realizar el cálculo automático de referencias.

Así que simplemente puedes llamar a Navigate () en tu hilo principal y nada va mal. El evento DocumentCompleted todavía se dispara en el subproceso de ayuda y puede pasar el rato jugando con el Documento en ese subproceso.

No estoy seguro de por qué todo esto es un problema, debería funcionar bien. Tal vez solo estabas desconcertado sobre su comportamiento. Si no, entonces esta respuesta podría ayudarte con una solución más universal. No temas a los nay-sayers demasiado por cierto, mostrar la interfaz de usuario en un subproceso de trabajo está lleno de trampas, pero en realidad nunca se muestra ninguna interfaz de usuario aquí y nunca se crea una ventana.

Esta respuesta se basa en la pregunta actualizada y los comentarios:

Básicamente, tengo que esperar a que se complete mi documento y la forma en que me gustaría hacerlo es con una clase AutoResetEvent () que se activará cuando se despida a DocumentComplete … y necesito que todo esto suceda en un hilo separado.

Soy consciente de que la interfaz de usuario principal se congelará. Esto sucederá solo una vez durante la vida útil de la aplicación (después de la inicialización). Estoy luchando para encontrar otra manera de hacer lo que estoy buscando lograr.

No creo que debas usar un hilo separado para esto. Puede deshabilitar la IU (por ejemplo, con un diálogo modal “Por favor, espere …”) y hacer el trabajo relacionado con WebBrowser en el hilo principal de la IU.

De todos modos, el siguiente código muestra cómo manejar un objeto WebBrowser en un subproceso STA separado. Se basa en la respuesta relacionada que publiqué recientemente, pero es compatible con .NET 4.0. Con .NET 4+, ya no necesita usar primitivas de sincronización de bajo nivel como AutoResetEvent . TaskCompletionSource lugar, use TaskCompletionSource , que permite propagar el resultado y las posibles excepciones al lado del consumidor de la operación.

 using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WinFroms_21790151 { public partial class MainForm : Form { public MainForm() { InitializeComponent(); this.Load += MainForm_Load; } void MainForm_Load(object senderLoad, EventArgs eLoad) { using (var apartment = new MessageLoopApartment()) { // create WebBrowser on a seprate thread with its own message loop var webBrowser = apartment.Invoke(() => new WebBrowser()); // navigate and wait for the result var bodyHtml = apartment.Invoke(() => { WebBrowserDocumentCompletedEventHandler handler = null; var pageLoadedTcs = new TaskCompletionSource(); handler = (s, e) => { try { webBrowser.DocumentCompleted -= handler; pageLoadedTcs.SetResult(webBrowser.Document.Body.InnerHtml); } catch (Exception ex) { pageLoadedTcs.SetException(ex); } }; webBrowser.DocumentCompleted += handler; webBrowser.Navigate("http://example.com"); // return Task return pageLoadedTcs.Task; }).Result; MessageBox.Show("body content:\n" + bodyHtml); // execute some JavaScript var documentHtml = apartment.Invoke(() => { // at least one script element must be present for eval to work var scriptElement = webBrowser.Document.CreateElement("script"); webBrowser.Document.Body.AppendChild(scriptElement); // inject and run some script var scriptResult = webBrowser.Document.InvokeScript("eval", new[] { "(function(){ return document.documentElement.outerHTML; })();" }); return scriptResult.ToString(); }); MessageBox.Show("document content:\n" + documentHtml); // dispose of webBrowser apartment.Invoke(() => webBrowser.Dispose()); webBrowser = null; } } // MessageLoopApartment public class MessageLoopApartment : IDisposable { Thread _thread; // the STA thread TaskScheduler _taskScheduler; // the STA thread's task scheduler public TaskScheduler TaskScheduler { get { return _taskScheduler; } } /// MessageLoopApartment constructor public MessageLoopApartment() { var tcs = new TaskCompletionSource(); // start an STA thread and gets a task scheduler _thread = new Thread(startArg => { EventHandler idleHandler = null; idleHandler = (s, e) => { // handle Application.Idle just once Application.Idle -= idleHandler; // return the task scheduler tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext()); }; // 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(); }); _thread.SetApartmentState(ApartmentState.STA); _thread.IsBackground = true; _thread.Start(); _taskScheduler = tcs.Task.Result; } /// shutdown the STA thread public void Dispose() { if (_taskScheduler != null) { var taskScheduler = _taskScheduler; _taskScheduler = null; // execute Application.ExitThread() on the STA thread Task.Factory.StartNew( () => Application.ExitThread(), CancellationToken.None, TaskCreationOptions.None, taskScheduler).Wait(); _thread.Join(); _thread = null; } } /// Task.Factory.StartNew wrappers public void Invoke(Action action) { Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Wait(); } public TResult Invoke(Func action) { return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result; } public Task Run(Action action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler); } public Task Run(Func action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler); } public Task Run(Func action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap(); } public Task Run(Func> action, CancellationToken token) { return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap(); } } } }