Trouble with timers and threads - c#

I'm a learn-by-example C# coder who isn't very advanced, which is why this problem is completely stumping me regardless of the amount of information on the internet.
I'm essentially creating a program that is, on a timer, repeatedly polling a website to get some information. During this process, a WebBrowser control is created to navigate to the information (needed for authentication). The program runs this series of events at startup, then using a System.Timers.Timer set to every 10 minutes (less for debugging of course) to do that same series of events yet when my Timer.Elapsed event triggers that process, I get a:
ThreadStateException with the description as ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.
Here is a slimmed down version of my program.
private void Form1_Load(object sender, EventArgs e)
{
GetDataFromWebBrowser();
Set_Auto_Refresh_Timer();
}
private void Set_Auto_Refresh_Timer()
{
System.Timers.Timer TimerRefresh = new System.Timers.Timer(10000);
TimerRefresh.Elapsed += new System.Timers.ElapsedEventHandler(TimerRefresh_Elapsed);
TimerRefresh.AutoReset = true;
TimerRefresh.Start();
}
private void TimerRefresh_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
GetDataFromWebBrowser();
}
private void GetDataFromWebBrowser()
{
WebBrowser wb = new WebBrowser(); <--This is where the error is thrown.
...get web data...
}
I think I got enough code in there to paint the picture. As you can see, when it gets to creating another WebBrowser, it throws the error.
I'm really stumped and I'm just starting to scrape the surface on Threading which is probably why I'm so stumped.
//Solution for me/
I ended up moving the WebBrowser creation out of the method as well as making it static to just reuse the WebBrowser control. I also swapped my System.Timers.Timer to System.Threading.Timer. Seemed to fix the problem.

The MSDN documentation for WebBrowser states that:
The WebBrowser class can only be used in threads set to single thread apartment (STA) mode. To use this class, ensure that your Main method is marked with the [STAThread] attribute.
Also, change your System.Timers.Timer to a System.Windows.Forms.Timer if you want to interact with UI controls in regular intervals. Alternatively, set the SynchronizingObject property of your System.Timers.Timer to a parent control to force your timer to invoke calls on the right thread. All WinForms controls can only be accessed from the same, one and only UI thread.
There are three types of timers in .NET's BCL, each of them acting very differently. Check this MSDN article for a comparison: Comparing the Timer Classes in the .NET Framework Class Library (web archive) or this brief comparison table.

I would recommend using WebClient class instead of WebBrowser. Also it seems to be better to store already created instance as a private property instead of creating new instance each time you need to poll a web site.
As following:
private WebClient webClient;
private void Form1_Load(object sender, EventArgs e)
{
GetDataFromWebBrowser();
Set_Auto_Refresh_Timer();
this.webClient = new WebClient();
}
private void Set_Auto_Refresh_Timer()
{
System.Timers.Timer.TimerRefresh = new System.Timers.Timer(10000);
TimerRefresh.Elapsed += new System.Timers.ElapsedEventHandler(TimerRefresh_Elapsed);
TimerRefresh.AutoReset = true;
TimerRefresh.Start();
}
private void Set_Auto_Refresh_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
GetDataFromWebBrowser();
}
private void GetDataFromWebBrowser()
{
...perform required work with webClient...
...get web data...
}

As Groo said, you should use System.Windows.Forms.Timer, or if you really want to do you operation in another thread, you should use the Invoke method to do any UI related stuff:
private void GetWebData()
{
...get web data...
}
private void ShowWebData()
{
WebBrowser wb = new WebBrowser();
// other UI stuff
}
private void TimerRefresh_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
GetDataFromWebBrowser();
}
private void GetDataFromWebBrowser()
{
GetWebData();
if (this.InvokeRequired)
this.Invoke(new Action(ShowWebData));
else
ShowWebData();
}

Related

Call Method From BackgroundWorker

I hate that my first question seems to have been answered many times, but I'm still having a tough time getting my head around how to call a method using BackgroundWorker.
I'm processing a very large text file using a series of classes and methods. The entire process is kicked off after the user selects a tool strip item. Sequentially, it goes like this:
User selects the tool strip item
User selects a file to be processed via a dialog box
The action starts
I think I can wrap everything into BackgroundWorker from the moment the user pops the initial dialog box, but what I'd like to do for now is just put the method where all the heavy lifting is done into its own instance of BackGroundWorker. I'll add a ProgressBar, too, but I think I can handle that if I can just get the BackgroundWorker process rolling.
From the top (pseudocode used for example purposes. Much omitted for brevity):
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
string fileName = openSingleFile.FileName;
processFile(fileName);
}
static public void processFile(string fileName)
{
// many vars/loops exist but not shown
foreach (data in bigData)
{
processItem(stringA, stringB); // <-- this method is where the expensive work is done
x++;
}
}
I've created an instance of BackgroundWorker...:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Things go here
}
...and I've tried too many things to list, so I've gone back to the beginning for the presentation above.
If I'm understanding BackgroundWorker, I'll need to do the following:
Replace processItem(stringA, stringB) in the above code with something like:
backgroundWorker1.RunWorkerAsync(processItem(stringA, stringB));
...and then do some type of DoWork call?
...and then do some type of RunWorkerCompleted call?
Not sure why my brain is freezing, but I'm embarrassed at the amount of time I've spent on this with no result. Any help would be greatly appreciated. Without StackOverflow, I would have been DOA a long time ago.
FYI: I've referenced other SO posts, MSDN, and DotNetPerls examples. I'm just missing something conceptually, I suppose.
Replace processItem(stringA, stringB) in the above code with something like...
No, that's how you got in trouble. You most definitely want to move the processFile() call to the worker. There is no perceivable benefit from running processItem() in a worker, at least not in the snippet you posted. And doing so is difficult, it would require starting more than one worker. One for each item. Having a lot of workers that each do little work is not very healthy. If it is really necessary then you don't want to use BackgroundWorker, you'll want an entirely different approach with several Threads that consume packets of work from a thread-safe queue. Don't go there if you can avoid it.
The only non-trivial problem to solve is passing the string that processFile() needs. Luckily BackgroundWorker.RunWorkerAsync() has an overload that takes a single object. Pass your string. Obtain its value in your DoWork event handler, casting e.Argument back to a string. Thus:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
string path = (string)e.Argument;
processFile(path);
}
private void processToolStripMenuItem_Click(object sender, EventArgs e) {
backgroundWorker1.RunWorkerAsync(openSingleFile.FileName);
processToolStripMenuItem.Enabled = false;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
processToolStripMenuItem.Enabled = true;
}
Starting up a new background worker is an expensive operation. You don't want to be starting one for each iteration of a loop. Instead, put the entire loop inside of a single background worker's scope.
When ToolStripMenuItem_Click is run create your background worker, have processFile be what is done in the DoWork event handler.
Make sure that when doing that work you're really just doing that work, not updating the UI. You'll want to separate business logic from the user interface. If you want to update the UI with some current progress then call ReportProgress and ensure that there is an event handler to properly update the UI.
If you need to update the UI when the work is all done then you can do so in the RunWorkerCompleted event handler. If the work you are doing generates some result that is used to update the UI use the Result property of the background worker to pass it from the DoWork method to the completed handler.
BackgroundWorker bgw;
In the Load event or constructor:
bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
//bgw.WorkerSupportsCancellation = true;
bgw.DoWork += bgw_DoWork;
bgw.ProgressChanged += bgw_ProgressChanged;
bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
/
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
string fileName = openSingleFile.FileName;
bgw.RunWorkerAsync(fileName);
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
string fileName = (string)e.Argument;
processFile(fileName);
}
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int Progress = e.ProgressPercentage;
//Update progressbar here
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Job completed
}

Why can I access controls from a form in a thread without cross thread exception?

Usually, when you access controls in a Thread you end up with some cross thread exceptions. In my C# WinForms Application I have a picture box and a toolstriplabel which do not cause that exception. I don't understand why, can anybody explain this to me?
Here some code explanation:
In the main form I have a picturebox and a toolstriplabel. Also I have a reference to another Form, which has no controls and no additional source code. And then in the main form there is another object which works with a thread. This thread can raise three different events and the main form is subscribed to these three events.
Event1 causes the toolstriplabel to update (with some information from the thread).
Event2 causes the picturebox to update (with a new picture from the thread).
Event1 and Event2 work perfectly fine. I do not use any invoke methods, I directly change Text and BackgroundImage properties without cross thread exception.
Event3 though makes troubles. It is supposed to show the other form but I receive the cross therad exception. It works only if I use a BeginInvoke to show the form.
Why is that?
Edit:
The multithreading is done by an MJPEGStream object. I subscribe the NewFrame method of that MJPEGStream object.
public partial class Form1 : Form
{
private CAM cam;
private PeekWindow frmPeekWindow;
public Form1()
{
InitializeComponent();
cam = new CAM();
cam.NewImageMessageEvent += new NewImageEventHandler(cam_NewImageMessageEvent);
cam.DetectionEvent += new DetectionEventHandler(cam_DetectionEvent);
cam.FpsChangedMessageEvent += new FpsChangedEventHandler(cam_FpsChangedMessageEvent);
cam.DetectionThreshold = (float)this.numDetectionThreshold.Value;
frmPeekWindow = new PeekWindow();
// without the next two lines, frmPeekwindow.Show() won't work if called in an event
frmPeekWindow.Show();
frmPeekWindow.Hide();
}
void cam_FpsChangedMessageEvent(object sender, FpsChangedEventArgs e)
{
lblFPS.Text = string.Format("fps: {0:0.0}", e.FPS);
}
void cam_DetectionEvent(object sender, DetectionEventArgs e)
{
if (chkEnablePeakWindow.Checked)
{
if (frmPeekWindow.InvokeRequired)
{
frmPeekWindow.Invoke((MethodInvoker)delegate()
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
});
}
else
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
}
}
}
void cam_NewImageMessageEvent(object sender, NewImageEventArgs e)
{
picStream.BackgroundImage = e.Image;
}
}
And here's the CAM class:
class CAM
{
private object lockScale = new object();
private MJPEGStream stream;
private Bitmap image;
public event NewImageEventHandler NewImageMessageEvent;
public event FpsChangedEventHandler FpsChangedMessageEvent;
public event DetectionEventHandler DetectionEvent;
// configure (login, pwd, source)
public CAM()
{
this.stream = new MJPEGStream("...");
this.stream.Login = "...";
this.stream.Password = "...";
this.stream.NewFrame += new NewFrameEventHandler(OnNewFrame)
}
private void OnNewFrame(object sender, NewFrameEventArgs ev)
{
try
{
FpsChangedMessageEvent(this, new FpsChangedEventArgs(10));
// get image
image = ev.Frame;
NewImageMessageEvent(this, new NewImageEventArgs(new Bitmap(image)));
DetectionEvent(this, new DetectionEventArgs(new Bitmap(image)));
}
catch (Exception ex)
{
Console.Out.WriteLine(ex.Message);
}
}
}
You won't get cross thread exception, but it doesn't mean that this is a safe operation. There is always a possibility for your control to go unstable. You just don't know when it will happen.
See the following explanation from Microsoft.
http://msdn.microsoft.com/en-us/library/ms171728.aspx
Access to Windows Forms controls is not inherently thread safe. If you
have two or more threads manipulating the state of a control, it is
possible to force the control into an inconsistent state. Other
thread-related bugs are possible, such as race conditions and
deadlocks. It is important to make sure that access to your controls
is performed in a thread-safe way.
I have these three possibilites in mind:
The action is already dispatched to the gui thread.
The action doesn't need to be dispatched currently.
The action is somehow executed from the gui thread.
It's most likely number 3.
You don't necessarily always have to call BeginInvoke/Invoke. Sometimes the operation is running on the foreground thread, sometimes it is in the background.
Per the microsoft samples that are everywhere, You can SHOULD check to see if calling BeginInvoke/Invoke is required.
private void SetTextStandardPattern()
{
if (this.InvokeRequired)
{
this.Invoke(SetTextStandardPattern);
return;
}
this.text = "New Text";
}
Here is a nice microsoft article that has a sample:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
and here is another article on how to "avoid" the pattern:
http://www.codeproject.com/Articles/37642/Avoiding-InvokeRequired

How to raise an event from an object that uses a timer back to the UI thread

I have an object that uses a timer to occasionally poll for a resource and then raises an event whenever the poll finds something of note. I have looked at several other examples but can't seem to find a method to marshall the event back to the UI thread without extra code on the event handler on the UI thread. So my question is:
Is there any way to hide this extra effort from the users of my object?
For the purpose of discussion I will include a trivial example:
Imagine I have a form with 1 richtextbox:
private void Form1_Load(object sender, EventArgs e)
{
var listener = new PollingListener();
listener.Polled += new EventHandler<EventArgs>(listener_Polled);
}
void listener_Polled(object sender, EventArgs e)
{
richTextBox1.Text += "Polled " + DateTime.Now.Second.ToString();
}
Also I have this object:
public class PollingListener
{
System.Timers.Timer timer = new System.Timers.Timer(1000);
public event EventHandler<EventArgs> Polled;
public PollingListener()
{
timer.Elapsed +=new System.Timers.ElapsedEventHandler(PollNow);
timer.Start();
}
void PollNow(object sender, EventArgs e)
{
var temp = Polled;
if (temp != null) Polled(this, new EventArgs());
}
}
If I run this, as expected it yields the exception
"Cross-thread operation not valid: Control 'richTextBox1' accessed
from a thread other than the thread it was created on"
This makes sense to me, and I can wrap the event handler method differently as so:
void listener_Polled(object sender, EventArgs e)
{
this.BeginInvoke(new Action(() => { UpdateText() }));
}
void UpdateText()
{
richTextBox1.Text += "Polled " + DateTime.Now.Second.ToString();
}
But now the user of my object has to do this for any event that is raised from the timer event in my control. So, is there anything I can add to my PollingListener class that doesn't change the signature of it's methods to pass in extra references that would allow the user of my object to be oblivious of the marshaling event in the background to the UI thread?
Thanks for any input you may have.
Added after comment:
You would need to pickup some latent detail that you can exploit to be able to accomplish that goal.
One thing that comes to mind is creating your own Forms/WPF timer at construction time and then use this and some synchronization to hide the details of coordination across threads. We can infer from your sample that construction of your poller should always happen in context of your consumer's thread.
This is a rather hack-ish way to accomplish what you want, but it can accomplish the deed because the construction of your poll-listener happens from the consumer's thread (which has a windows message pump to fuel the dispatches of Forms/WPF timers), and the rest of the operation of the class could occur from any thread as the forms Timer's tick will heartbeat from the original thread. As other comments and answers have noted, it would be best to reassess and fix the operating relationship between your polling operations and the consumer.
Here is an updated version of the class, PollingListener2 that uses a ManualResetEvent and a concealed System.Windows.Forms.Timer to ferry the polling notice across threads. Cleanup code is omitted for the sake of brevity. Requiring the use of IDisposable for explicit cleanup would be recommended in a production version of this class.
ManualResetEvent # MSDN
public class PollingListener2
{
System.Timers.Timer timer = new System.Timers.Timer(1000);
public event EventHandler<EventArgs> Polled;
System.Windows.Forms.Timer formsTimer;
public System.Threading.ManualResetEvent pollNotice;
public PollingListener2()
{
pollNotice = new System.Threading.ManualResetEvent(false);
formsTimer = new System.Windows.Forms.Timer();
formsTimer.Interval = 100;
formsTimer.Tick += new EventHandler(formsTimer_Tick);
formsTimer.Start();
timer.Elapsed += new System.Timers.ElapsedEventHandler(PollNow);
timer.Start();
}
void formsTimer_Tick(object sender, EventArgs e)
{
if (pollNotice.WaitOne(0))
{
pollNotice.Reset();
var temp = Polled;
if (temp != null)
{
Polled(this, new EventArgs());
}
}
}
void PollNow(object sender, EventArgs e)
{
pollNotice.Set();
}
}
This has some precedent in the distant Win32 past where some people would use hidden windows and the like to maintain one foot in the other thread without requiring the consumer to make any significant changes to their code (sometimes no changes are necessary).
Original:
You could add a member variable on your helper class of type Control or Form and use that as the scope for a BeginInvoke() / Invoke() call on your event dispatch.
Here's a copy of your sample class, modified to behave in this manner.
public class PollingListener
{
System.Timers.Timer timer = new System.Timers.Timer(1000);
public event EventHandler<EventArgs> Polled;
public PollingListener(System.Windows.Forms.Control consumer)
{
timer.Elapsed += new System.Timers.ElapsedEventHandler(PollNow);
timer.Start();
consumerContext = consumer;
}
System.Windows.Forms.Control consumerContext;
void PollNow(object sender, EventArgs e)
{
var temp = Polled;
if ((temp != null) && (null != consumerContext))
{
consumerContext.BeginInvoke(new Action(() =>
{
Polled(this, new EventArgs());
}));
}
}
}
Here's a sample that shows this in action. Run this in debug mode and look at your output to verify that it is working as expected.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listener = new PollingListener(this);
}
PollingListener listener;
private void Form1_Load(object sender, EventArgs e)
{
listener.Polled += new EventHandler<EventArgs>(listener_Poll);
}
void listener_Poll(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("ding.");
}
}
If the processing work inside your PollNow is fairly small then you do not need to perform it on a separate thread. If WinForms use Timer, in WPF you use DispatchTimer and then you are performing the test on the same thread as the UI and there is no cross-thread issue.
This SO question prompted this comment:
I think this excerpt is enlightening: "Unlike the
System.Windows.Forms.Timer, the System.Timers.Timer class will, by
default, call your timer event handler on a worker thread obtained
from the common language runtime (CLR) thread pool. [...] The
System.Timers.Timer class provides an easy way to deal with this
dilemma—it exposes a public SynchronizingObject property. Setting this
property to an instance of a Windows Form (or a control on a Windows
Form) will ensure that the code in your Elapsed event handler runs on
the same thread on which the SynchronizingObject was instantiated."
And System.Times.Timer doc says of SynchronizingObject:
Gets or sets the object used to marshal event-handler calls that are
issued when an interval has elapsed.
Both of which implie that if you pass a control created on the UI thread as the sync object then the timer will effectively marshal the timer event calls to the UI thread.

I get an error about using a different thread?

When I click my ActionButton, there is a timer that starts and after 3 seconds, it must fire a methode to change the current ContentPage to the another page.
But i get a message : The calling thread cannot access this object because a different thread owns it. I dont understand what i am doing wrong. But if i put the ChangeContent() method in the click_event, it works, but in the _tm_elapsed it doenst work?
using smartHome2011.FramePages;
using System.Timers;
public partial class AuthenticationPage : UserControl
{
private MainWindow _main;
private Storyboard _storyboard;
private Timer _tm = new Timer();
private HomeScreen _homeScreen = new HomeScreen();
public AuthenticationPage(MainWindow mainP)
{
this.InitializeComponent();
_main = mainP;
}
private void ActionButton_Click(object sender, System.EventArgs eventArgs)
{
_main.TakePicture();
identifyBox.Source = _main.source.Clone();
scanningLabel.Visibility = Visibility.Visible;
_storyboard = (Storyboard) FindResource("scanningSB");
//_storyboard.Begin();
Start();
}
private void Start()
{
_tm = new Timer(3000);
_tm.Elapsed += new ElapsedEventHandler(_tm_Elapsed);
_tm.Enabled = true;
}
private void _tm_Elapsed(object sender, ElapsedEventArgs e)
{
((Timer) sender).Enabled = false;
ChangeContent();
//MessageBox.Show("ok");
}
private void ChangeContent()
{
_main.ContentPage.Children.Clear();
_main.ContentPage.Children.Add(_homeScreen);
}
}
Description
You have to use Invoke to ensure that the UI Thread (the thread who has created your Control) will execute that.
1. If you are doing Windows Forms then do this
Sample
private void ChangeContent()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(ChangeContent));
return;
}
_main.ContentPage.Children.Clear();
_main.ContentPage.Children.Add(_homeScreen);
}
2. If you are doing WPF then do this
private void _tm_Elapsed(object sender, ElapsedEventArgs e)
{
((Timer) sender).Enabled = false;
this.Dispatcher.Invoke(new Action(ChangeContent), null);
//MessageBox.Show("ok");
}
More Information
Windows Forms
MSDN - Control.Invoke Method
MSDN - Control.InvokeRequired Property
WPF
MSDN - Dispatcher.Invoke Method
MSDN - Dispatcher Class
The logic executed in the Elapsed event of the Timer is run on a separate thread from the rest of your code. This thread cannot access objects on the main/GUI thread.
This thread should help you find out how to do it: How to update the GUI from another thread in C#?
I suspect you are using a System.Threading.Timer. You can avoid cross thread operation by just using a Windows.Forms timer:
http://msdn.microsoft.com/en-us/library/system.windows.forms.timer.aspx
That timer uses regular messages and the event occours on the same thread of the UI.
The event to use is no more called "Elapsed", but "Tick" read the doc here: http://msdn.microsoft.com/en-us/library/system.windows.forms.timer.tick.aspx

Wrapping an asynchronous method synchronously in C#

I have a third party library containing a class which performs a function asynchronously. The class inherits from the Form. The function basically performs a calculation based on data stored in a database. Once it has finished, it calls a _Complete event in the calling form.
What I would like to do is call the function synchronously but from a non-windows form application. The problem is, no matter what I do, my application blocks and the _Complete event handler never fires. From a windows form I can simulate the function running synchronously by using a "complete" flag and a "while (!complete) application.doevents", but obviously application.doevents isnt available in a non-windows form application.
Is there something that would stop me using the class's method outside of a windows form application (due to it inheriting from 'Form') ?
Is there some way I can work around this ?
Thanks,
Mike
At a stab it might be worth trying something like the following which uses a WaitHandle to block the current thread rather than spinning and checking a flag.
using System;
using System.Threading;
class Program
{
AutoResetEvent _autoEvent;
static void Main()
{
Program p = new Program();
p.RunWidget();
}
public Program()
{
_autoEvent = new AutoResetEvent(false);
}
public void RunWidget()
{
ThirdParty widget = new ThirdParty();
widget.Completed += new EventHandler(this.Widget_Completed);
widget.DoWork();
// Waits for signal that work is done
_autoEvent.WaitOne();
}
// Assumes that some kind of args are passed by the event
public void Widget_Completed(object sender, EventArgs e)
{
_autoEvent.Set();
}
}
I've got some more information on this problem (I'm working in the same team as mikecamimo).
The problem also occurs in the Windows Forms application, when replicated correctly. In the original OP, the problem didn't occur in the windows form because there was no blocking. When blocking is introduced by using a ResetEvent, the same problem occurs.
This is because the event handler (Widget_Completed) is on the same thread as the method calling Widget.DoWork. The result that AutoResetEvent.WaitOne(); blocks forever because the event handler is never called to Set the event.
In a windows forms environment this can worked around by using Application.DoEvents to poll the message queue and allow the event the be handled. See below.
using System;
using System.Threading;
using System.Windows.Forms;
class Program
{
EventArgs data;
static void Main()
{
Program p = new Program();
p.RunWidget();
}
public Program()
{
_autoEvent = new AutoResetEvent(false);
}
public void RunWidget()
{
ThirdParty widget = new ThirdParty();
widget.Completed += new EventHandler(this.Widget_Completed);
data = null;
widget.DoWork();
while (data == null);
Application.DoEvents();
// do stuff with the results of DoWork that are contained in EventArgs.
}
// Assumes that some kind of args are passed by the event
public void Widget_Completed(object sender, EventArgs e)
{
data = e;
}
}
In a non windows forms application, such as a Windows Service, Application is not available so DoEvents cannot be called.
The problem is one of threading and that widget.DoWork's associated event handler somehow needs to be on another thread. This should prevent AutoResetEvent.WaitOne from blocking indefinitely. I think... :)
Any ideas on how to accomplish this would be fantastic.
AutoResetEvent _autoEvent = new AutoResetEvent(false);
public WebBrowser SyncronNavigation(string url)
{
WebBrowser wb = null;
wb = new WebBrowser();
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);
wb.ScriptErrorsSuppressed = true;
wb.Navigate(new Uri(url));
while (!_autoEvent.WaitOne(100))
Application.DoEvents();
return wb;
}
void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//throw new NotImplementedException();
_autoEvent.Set();
}
Do you have the source for the component? It sounds like it's relying on the fact it will be called from a WinForms environment (must be a good reason why a library inherits from Form!), but it's hard to know for sure.

Categories