I'm trying to use this control inside a class library, but when I run the code below, I did not see a request to google being sent (using fiddler).
public class WebBrowserTest
{
public WebBrowserTest()
{
var t = new Thread(StartBrowser);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void StartBrowser()
{
WebBrowser web;
web = new WebBrowser();
web.Navigate("http://www.google.com");
}
}
My guess is this has something to do with threading, and possibly the thread ending before the control gets a chance to send the request. But I have no idea where to start with solving this.
THE SOLUTION
I found this solution to work, the events are getting fired and the main thread waits for the STA thread.
public class WebThread
{
private WebBrowser web { get; set; }
public void StartBrowser()
{
web = new WebBrowser();
web.Visible = true;
web.DocumentCompleted += Web_DocumentCompleted;
web.ScriptErrorsSuppressed = true;
web.Navigate("http://www.google.com");
Application.Run();
web.Dispose();
}
private void Web_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
Debug.Print("Arrived: " + e.Url.ToString());
if (e.Url.ToString() == "http://www.google.com.au/")
{
Application.ExitThread();
}
}
}
public class WebBrowserTest
{
public WebBrowserTest()
{
Debug.Print("Thread is starting.");
var webThread = new WebThread();
var t = new Thread(webThread.StartBrowser);
t.SetApartmentState(ApartmentState.STA);
t.Start();
while(t.IsAlive)
{
Thread.Sleep(5000);
}
Debug.Print("Thread has finished.");
}
}
WebBrowser.Navigate( ... ) doesn't block - it returns immediately, before the request is sent. Since your thread function then exits, your whole thread ends and takes your WebBrowser control with it.
If you're just trying to download a web page, have a look at the WebClient class. It has many async methods which means you probably won't even have to create your own thread.
Related
I have an old application in Windows Forms, which in many places do some searches on database. Sometimes it takes a lot of time, so I decided to create a loading screen in wpf to show the user that something is loading in separate thread. Basically it's just a full transparent window with loading indicator(a turning circle). Everything works fine on My host computer and on my Virtual Machine, but when I'm trying to deploy it to our demo environments its like - it starts loading the indicator is shown and after few seconds it dissapear and application stops responding like forever. My first thought was that it's the problem with GPU acceleration, that it can't process transparency, but it's being shown for few seconds so it can't be the problem. So most likely I did something bad. Below You can see my code, do You notice something which might be wrong/cause deadlock or something ?
public class LoadingManager
{
public LoadingManager()
{ }
public LoadingManager(string LoadingText)
{
loadingText = LoadingText;
}
private string loadingText = "Please wait ..";
private Thread thread;
private bool ThreadReadyToAbort = false;
private BusyIndicatorView loadingWindow;
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
}
public void EndLoading()
{
if (this.loadingWindow != null)
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{ this.loadingWindow.Close(); }));
while (!this.ThreadReadyToAbort) { }; // I also tried to remove this while but it didn't help
}
this.thread.Abort();
}
public void RunThread()
{
this.loadingWindow = new BusyIndicatorView();
loadingWindow.tbLoadingCaption.Text = loadingText;
this.loadingWindow.Closing += new System.ComponentModel.CancelEventHandler(waitingWindow_Closed);
this.loadingWindow.ShowDialog();
}
void waitingWindow_Closed(object sender, System.ComponentModel.CancelEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeShutdown();
this.ThreadReadyToAbort = true;
}
EDIT.
I noticed that on this machines it usually(sometimes it also fails at first click) works when i click search for the first time. If i click another time it's showing for a second than dissapearing and application stops responding. So it seems like Thread is not beeing shutdown, Dispatcher shutdown failed ? But no exceptions are thrown ...
Your BeginLoading method can be called more than once before it has finished, and so can create more than one wpf window. This messes up all kinds of references. Also do not abort the thread, let it decide for itself. Here are the two changes:
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
while (this.loadingWindow == null) { } // <--- Add this line
}
public void EndLoading()
{
if (this.loadingWindow != null)
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{ this.loadingWindow.Close(); }));
while (!this.ThreadReadyToAbort) { };
}
//this.thread.Abort(); // <-- Remove this line
}
Also if this is all just for a busy screen, I would say there has to be a better and safer way than this. For academic purposes this is an interesting problem, but not production code, especially if some junior developer could fiddle with this in the future.
Edit: Still crashing on my machine if I reduce the delay between repeated callds to BeginLoading and EndLoading. It may be related to how the wpf window closes asynchronously. I removed the Closed event handler and just used a boolean flag to indicated that the window 'isLoaded' or not, and I have not seen any problems with this:
public class LoadingManager2
{
public LoadingManager2()
{ }
public LoadingManager2(string LoadingText)
{
loadingText = LoadingText;
}
private string loadingText = "Please wait ..";
private Thread thread;
private MyWindow loadingWindow;
private bool isLoaded = false;
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
while (!this.isLoaded) { };
}
public void RunThread()
{
this.loadingWindow = new MyWindow();
this.isLoaded = true;
this.loadingWindow.ShowDialog();
}
public void EndLoading()
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{
this.loadingWindow.Close();
this.isLoaded = false;
}));
while (this.isLoaded) { };
}
}
Working on a C# project which I would like to implement a "waiting" (throbber) indicator in a separate form. After much research and trial and error it appears as the suggested method of doing this is to load a form using a separate thread from the one from the current form/thread.
The reason I went with this method was because initially using the Show() method on the throbber form produced a transparent form. I cannot use ShowDialog because I need to run some code after the throbber is displayed, after which that completes I would like to close the throbber form.
Anyway .. after trying many different methods to load the throbber form in a separate thread I still get an error about trying to access it from a thread which is different from the one it was created in. Here is a skelton version of the project code that should shed some light on my issue:
the example I was working off of for multithreading was this popular link for creating your own spashscreen in a separate thread ... http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C
public class Main
{
public void CheckData()
{
try
{
ProgressBar pb = new ProgressBar();
pb.ShowProgressBar();
//do data checking here
pb.CloseForm()
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static Thread ms_oThread = null;
public bool shouldStop = false;
static ProgressBar ms_ProgBar = null;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
public void ShowForm()
{
ms_ProgBar = new ProgressBar();
Application.Run(ms_ProgBar);
}
public void CloseForm()
{
ms_ProgBar.Close();
}
public void ShowProgressBar()
{
// Make sure it is only launched once.
if (ms_ProgBar != null)
return;
ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
while (ms_ProgBar == null || ms_ProgBar.IsHandleCreated == false)
{
System.Threading.Thread.Sleep(1000);
}
}
}
You are creating your ProgressBar twice. Once in your main function, and once in your new thread. You are also calling your CloseWindow method from your main function (and on the window that is never shown), rather than on your new thread window.
You only want to create ProgressBar and show it using your new thread. Make your static ProgressBar field public so you can call close on it directly from Main, but make sure to use Invoke to do it since it's not on that Window's GUI thread.
Also, ShowProgressBar should be static.
Here's a rewrite attempt:
public class Main
{
public void CheckData()
{
try
{
ProgressBar.ShowProgressBar();
//do data checking here
ProgressBar.CloseForm();
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static ProgressBar _progressBarInstance;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
static void ShowForm()
{
_progressBarInstance = new ProgressBar();
Application.Run(ms_ProgressBar);
}
public static void CloseForm()
{
_progressBarInstance.Invoke(new Action(_progressBarInstance.Close));
_progressBarInstance= null;
}
public static void ShowProgressBar()
{
// Make sure it is only launched once.
if (_progressBarInstance != null)
return;
var ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
}
}
I have a FTP proccess that run without UI. and have a winform that use this ftp control. in that window I have a progressbar that show the ftp upload progress. The progress arrives to the window via interfase that is updated on the underliying presenter (I'm using MVP pattern).
My problem is when try to update the progress, it allways throw me this exception.
Through threads illegal operation: control 'prgProgresoSubido' is accessed from a thread other than that in which you created it.
That problem persists even if I use a BackGroundWorker in the Form.
// This is a delegated on presenter when a File finish to upload
void client_FileUploadCompletedHandler(object sender, FileUploadCompletedEventArgs e)
{
string log = string.Format("{0} Upload from {1} to {2} is completed. Length: {3}. ",
DateTime.Now, e.LocalFile.FullName, e.ServerPath, e.LocalFile.Length);
archivosSubidos += 1;
_Publicacion.ProgresoSubida = (int)((archivosSubidos / archivosXSubir) * 100);
//this.lstLog.Items.Add(log);
//this.lstLog.SelectedIndex = this.lstLog.Items.Count - 1;
}
// This is My interfase
public interface IPublicacion
{
...
int ProgresoSubida { set; }
}
/// And Here is the implementartion of the interfase on the form
public partial class PublicarForm : Form ,IPublicacion
{
//Credenciales para conectarse al servicio FTP
public FTPClientManager client = null;
public XmlDocument conf = new XmlDocument();
public string workingDir = null;
public webTalk wt = new webTalk();
private readonly PublicacionesWebBL _Publicador;
public PublicarForm()
{
InitializeComponent();
String[] laPath = { System.AppDomain.CurrentDomain.BaseDirectory};
String lcPath = System.IO.Path.Combine(laPath);
_Publicador = new PublicacionesWebBL(this, lcPath);
}
public int ProgresoSubida
{
set
{
// This is my prograss bar, here it throw the exception.
prgProgresoSubido.Value = value;
}
}
}
How can I do to avoid this problem ?
In general, all updates to the User Interface and Controls has to be done from the main thread (event dispatcher). If you attempt to modify the properties of a control from a different thread you will get an exception.
You must call Control.Invoke to invoke on the event dispatcher the method that updates your UI
Control.Invoke
Here, place a button and a label on a form, then try this
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(TestThread));
t.Start();
}
private void TestThread()
{
for (int i = 0; i < 10; i++)
{
UpdateCounter(i);
Thread.Sleep(1000);
}
}
private void UpdateCounter(int i)
{
if (label1.InvokeRequired)
{
label1.Invoke(new ThreadStart(delegate { UpdateCounter(i); }));
}
else
{
label1.Text = i.ToString();
}
}
}
Realize, that if you are firing an event from a thread, that the event will be on the same Thread. Therefore, if that thread is not the event dispatcher, you'll need to invoke.
Also, there may be mechanisms that BackgroundWorker gives you (As the commentator said) that simplify this for you, but I've never used it before so I'll leave that up to you to investigate.
As Alan has just pointed out, you must do all operations with UI controls in UI thread.
Just modify your property like this:
public int ProgresoSubida
{
set
{
MethodInvoker invoker = delegate
{
prgProgresoSubido.Value = value;
}
if (this.InvokeRequired)
{
Invoke(invoker);
}
else
{
invoker();
}
}
}
In it's simplicity what I am trying to do is handle "Doing Something" by firing off a process on a seperate thread to do what I need to do and waiting for an event to be raised to say "I have finished doing what I need to do". In the EventArgs though I will have a property for any errors which may be encountered during the process. Here is a simplified example of my situation.
public class MessageHandler
{
private AutoResetEvent MessageHasSent = new AutoResetEvent(false);
public void SendMessage()
{
MessageSender ms = new MessageSender();
ms.MessageSent += new EventHandler<MessageSentEventArgs>(MessageHandler_MessageSent);
Thread t = new Thread(ms.Send());
t.Start();
MessageHasSent.WaitOne();
//Do some check here
//Same again but for "Message recieved"
}
void MessageHandler_MessageSent(object sender, MessageSentEventArgs e)
{
if (e.Errors.Count != 0)
{
//What can I do here to return to the next step after waitone?
}
else
MessageHasSent.Set();
}
}
public class MessageSender
{
public event EventHandler<MessageSentEventArgs> MessageSent;
public void Send()
{
//Do some method which could potentiallialy return a List<Error>
MessageSent(this, new MessageSentEventArgs() { Errors = new List<Error>() });
}
}
public class Error { }
public class MessageSentEventArgs : EventArgs
{
public List<Error> Errors;
}
Essentially once the event has been raised from Send the code will continute, however I want some way of the event giving feedback, potentially using the MessageHasSent. I have tried different methods, I thought if I called Close instead of Set it would perhaps allow me to access something such as IsClosed. You could throw an exception or set a flag outside of the scope of the event to check but I feel like this is dirty.
Any suggestions?
Using the TPL isn't applicable in my case as I am using .NET 3.5.
Since it seems that this entire section of code is already running in a background thread, and you're doing nothing more than starting up a new thread just so that you can wait for it to finish, you'd be better off just calling Send directly, rather than asynchronously.
You don't need to fire off an event when you're completed.
You don't need to signal the main thread when it needs to continue.
You don't need to log the exceptions in a List, you can just throw them and catch them in SendMessage with a try/catch block.
This will do what you want:
public class MessageHandler
{
private AutoResetEvent MessageHasSent = new AutoResetEvent(false);
private bool IsSuccess = false;
public void SendMessage()
{
MessageSender ms = new MessageSender();
ms.MessageSent += new EventHandler<MessageSentEventArgs>(MessageHandler_MessageSent);
Thread t = new Thread(ms.Send());
t.Start();
MessageHasSent.WaitOne();
if(IsSuccess)
//wohooo
else
//oh crap
//Same again but for "Message recieved"
}
void MessageHandler_MessageSent(object sender, MessageSentEventArgs e)
{
IsSuccess = e.Errors.Count == 0;
MessageHasSent.Set();
}
}
public class MessageSender
{
public event EventHandler<MessageSentEventArgs> MessageSent;
public void Send()
{
//Do some method which could potentiallialy return a List<Error>
MessageSent(this, new MessageSentEventArgs() { Errors = new List<Error>() });
}
}
public class Error { }
public class MessageSentEventArgs : EventArgs
{
public List<Error> Errors;
}
This question already has answers here:
WebBrowser Control in a new thread
(4 answers)
Closed 7 years ago.
I want to make 3 threads that each run the WebBroswer control. So I would like to use the ThreadPool to make things easy.
for(int i = 0;i < 3;i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(gotoWork), i));
}
WaitAll(waitHandles);
....../
void gotoWork(object o)
{
string url = String.Empty;
int num = (int)o;
switch(num)
{
case 0:
url = "google.com";
break;
case 1:
url = "yahoo.com";
break;
case 2:
url = "bing.com";
break;
}
WebBrowser w = new WebBrower();
w.Navigate(url);
}
But I get an error saying that I need a STA thread which the ThreadPool will never be. Before trying this method I tried this.
Thread[] threads = Thread[3];
for(int i = 0;i < 3;i++)
{
threads[i] = new Thread(new ParameterizedStart(gotoWork);
threads[i] = SetApartmentState(ApartmentState.STA); //whoo hoo
threads[i] = Start();
}
for(int i = 0; i < 3;i++)
{
threads[i].Join();
}
And the WebBrowsers all initialized and everything looks good but only one more two will actually do anything and its never consistant at all. Threading has been such a nightmare. Can anybody suggest a nice alternative?
public sealed class SiteHelper : Form
{
public WebBrowser mBrowser = new WebBrowser();
ManualResetEvent mStart;
public event CompletedCallback Completed;
public SiteHelper(ManualResetEvent start)
{
mBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(mBrowser_DocumentCompleted);
mStart = start;
}
void mBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Generated completed event
Completed(mBrowser);
}
public void Navigate(string url)
{
// Start navigating
this.BeginInvoke(new Action(() => mBrowser.Navigate(url)));
}
public void Terminate()
{
// Shutdown form and message loop
this.Invoke(new Action(() => this.Close()));
}
protected override void SetVisibleCore(bool value)
{
if (!IsHandleCreated)
{
// First-time init, create handle and wait for message pump to run
this.CreateHandle();
this.BeginInvoke(new Action(() => mStart.Set()));
}
// Keep form hidden
value = false;
base.SetVisibleCore(value);
}
}
An other class as
public abstract class SiteManager : IDisposable
{
private ManualResetEvent mStart;
private SiteHelper mSyncProvider;
public event CompletedCallback Completed;
public SiteManager()
{
// Start the thread, wait for it to initialize
mStart = new ManualResetEvent(false);
Thread t = new Thread(startPump);
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
mStart.WaitOne();
}
public void Dispose()
{
// Shutdown message loop and thread
mSyncProvider.Terminate();
}
public void Navigate(string url)
{
// Start navigating to a URL
mSyncProvider.Navigate(url);
}
public void mSyncProvider_Completed(WebBrowser wb)
{
// Navigation completed, raise event
CompletedCallback handler = Completed;
if (handler != null)
{
handler(wb);
}
}
private void startPump()
{
// Start the message loop
mSyncProvider = new SiteHelper(mStart);
mSyncProvider.Completed += mSyncProvider_Completed;
Application.Run(mSyncProvider);
}
}
class Tester :SiteManager
{
public Tester()
{
SiteEventArgs ar = new SiteEventArgs("MeSite");
base.Completed += new CompletedCallback(Tester_Completed);
}
void Tester_Completed(WebBrowser wb)
{
MessageBox.Show("Tester");
if( wb.DocumentTitle == "Hi")
base.mSyncProvider_Completed(wb);
}
//protected override void mSyncProvider_Completed(WebBrowser wb)
//{
// // MessageBox.Show("overload Tester");
// //base.mSyncProvider_Completed(wb, ar);
//}
}
On your main form:
private void button1_Click(object sender, EventArgs e)
{
//Tester pump = new Tester();
//pump.Completed += new CompletedCallback(pump_Completed);
//pump.Navigate("www.cnn.com");
Tester pump2 = new Tester();
pump2.Completed += new CompletedCallback(pump_Completed);
pump2.Navigate("www.google.com");
}
You need to host webbrowser control in somewhere to get it work (add it to a form), secondly you should use Invoke() of the host control (or a similar delegate) such as Form.Invoke() to interact with WebBrowser control.
No need to remind now but yes, your threads should be STA
You can use such type of class for your purpose to host WebBrowser control:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SomeNameSpace
{
public class WebForm : Form
{
public WebBrowser WebBrowser { get; set; }
public WebForm()
{
WebBrowser = new WebBrowser();
}
}
}
After all I choose this solution:
http://watin.sourceforge.net/documentation.html
In your case you should do this:
...
Thread thread = new Thread(SomeMethod);
thread.SetApartmentState(ApartmentState.STA);
...