I am just asking for the cleanest way to have a callback to update the GUI when a thread is running and a method that will be called when the thread has finished.
So in my example, I have a Counter class (this is the task), and 2 delegates, 1 for callback, 1 for finishing of the thread.
It all works fine, but I have the feeling that this is not the best/cleanest/.. way to do it.
any suggestions would be greatly appreciated, I'm having a hard time understanding the concept of a delegate and threads threads since I haven't started programming that long ago.
The counter class
class Counter
{
private PrintCallback cb;
private OnActionFinish oaf;
public void SetCallback(PrintCallback c)
{
this.cb = c;
}
public void SetOnFinished(OnActionFinish f)
{
this.oaf = f;
}
public void Count()
{
for (int i = 0; i < 1000; i++)
{
cb(i);
}
oaf();
}
}
And my main form
public delegate void PrintCallback(int i);
public delegate void OnActionFinish();
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
PrintCallback cb = new PrintCallback(Print);
OnActionFinish otf = new OnActionFinish(Finished);
Counter c = new Counter();
c.SetCallback(cb);
c.SetOnFinished(otf);
Thread t = new Thread(c.Count);
t.Start();
label1.Text = "Thread started";
}
private void Print(int i)
{
if (textBox1.InvokeRequired)
{
textBox1.Invoke((MethodInvoker)delegate {
textBox1.Text += i + "\r\n";
});
}
else
{
textBox1.Text += i + "\n";
}
}
private void Finished()
{
if (label1.InvokeRequired)
{
label1.Invoke((MethodInvoker)delegate {
label1.Text = "Thread finished";
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
});
}
else
{
label1.Text = "Thread finished";
}
}
To refactor your code, using your own techniques, it could become something like this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
label1.Text = "Thread started";
Counter c = new Counter(Print, Finished);
new Thread(new ThreadStart(c.Count)).Start();
}
private void Print(int i)
{
if (InvokeRequired)
Invoke(new Action<int>(Print), i);
else
textBox1.Text += i + "\r\n";
}
private void Finished()
{
if (InvokeRequired)
{
Invoke(new Action(Finished));
}
else
{
label1.Text = "Thread finished";
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
}
}
}
class Counter
{
private readonly Action<int> _output;
private readonly Action _finished;
public Counter(Action<int> output, Action finished)
{
_output = output;
_finished = finished;
}
public void Count()
{
for (int i = 0; i < 1000; i++)
_output(i);
_finished();
}
}
Related
I'm searching for this for several hours now and was not able to find proper solution. I'm c# beginner.
I have a winforms app with a ListBox and a class that does some work and should run forever on separate thread. I want to push MyDataStruct to ListBox each time its created in WorkerClass.Work.
Later on, several WorkerClass instances should run simultaneously and I will have combobox to pick which instance data to feed to ListBox . Is it better to have WorkerClas return only single MyDataStruct and keep their queue in Form1 class or have a queue in each WorkerClass and exchange the entire queue with Form1 every time it changes?
is my void QueueToLb good way to add queue data to ListBox ?
thank you for your support.
public partial class Form1 : Form
{
Queue<MyDataStruct> qList;
MyDataStruct myDataStruct;
private void RunTask()
{
//how do I make MyLongTask to update either qList or myDataStuct
Task.Run(() =>
{
MyLongTask(0, 1000);
});
}
private void MyLongTask(int low, int high)
{
WorkerClass wc = new WorkerClass();
wc.Work(low,high);
}
private void QueueToLb()
{
//is this good way to update listbox from queue?
List<MyDataStruct> lstMds = qList.Reverse<MyDataStruct>().ToList<MyDataStruct>();
List<string> lstStr = new List<string>();
foreach (MyDataStruct m in lstMds)
{
lstStr.Add(m.ToString());
}
listBox1.DataSource = lstStr;
}
}
public class WorkerClass
{
Queue<MyDataStruct> qList; //not sure if its better to keep the queue here or in Form1
public WorkerClass()
{
qList = new Queue<MyDataStruct>();
}
public void Work(int low, int high) //does some work forever
{
while (true)
{
if (qList.Count > 11) qList.Dequeue();
MyDataStruct mds = new MyDataStruct();
Random random = new Random();
mds.dt = DateTime.Now;
mds.num = random.Next(low, high);
qList.Enqueue(mds);
Thread.Sleep(1000);
}
}
}
public class MyDataStruct
{
public DateTime dt;
public int num;
public override string ToString()
{
StringBuilder s = new StringBuilder();
s.Append(num.ToString());
s.Append(" - ");
s.Append(dt.ToShortDateString());
return s.ToString();
}
}
OK I think I figured how to use BackgroundWorker on this, I'll be happy if someone could verify it is correct
public partial class Form1 : Form
{
Queue<MyDataStruct> qList;
BackgroundWorker bw = new BackgroundWorker();
public Form1()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(Bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
}
private void Form1_Load(object sender, EventArgs e)
{
qList = new Queue<MyDataStruct>(12);
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync();
}
private void MyLongTask(int low = 0, int high = 1000)
{
WorkerClass wc = new WorkerClass(bw);
wc.Work(low,high);
}
private void BindToLbWithQueue()
{
MyDataStruct mds = new MyDataStruct();
Random random = new Random();
mds.dt = DateTime.Now;
mds.num = random.Next(0, 1000);
qList.Enqueue(mds);
QueueToLb();
}
private void QueueToLb()
{
//is this good way to update listbox from queue?
List<MyDataStruct> lstMds = qList.Reverse<MyDataStruct>().ToList<MyDataStruct>();
List<string> lstStr = new List<string>();
foreach (MyDataStruct m in lstMds)
{
lstStr.Add(m.ToString());
}
listBox1.DataSource = lstStr;
}
#region worker
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
MyLongTask();
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
qList = (Queue<MyDataStruct>)e.UserState;
QueueToLb();
}
#endregion
}
public class WorkerClass
{
Queue<MyDataStruct> qList; //not sure if its better to keep the queue here or in Form1
BackgroundWorker bw = null;
public WorkerClass(BackgroundWorker bw)
{
this.bw = bw;
qList = new Queue<MyDataStruct>();
}
public void Work(int low, int high) //does some work forever
{
while (true)
{
if (qList.Count > 11) qList.Dequeue();
MyDataStruct mds = new MyDataStruct();
Random random = new Random();
mds.dt = DateTime.Now;
mds.num = random.Next(low, high);
qList.Enqueue(mds);
bw.ReportProgress(0, qList);
Thread.Sleep(1000);
}
}
}
public class MyDataStruct
{
public DateTime dt;
public int num;
public override string ToString()
{
StringBuilder s = new StringBuilder();
s.Append(num.ToString());
s.Append(" - ");
s.Append(dt.ToShortDateString());
return s.ToString();
}
}
I am calculating prime numbers bw two numbers using following code
private static IEnumerable<int> GetPrimes(int from, int to)
{
for (int i = from; i <= to; i++)
{
bool isPrime = true;
int limit = (int)Math.Sqrt(i);
for (int j = 2; j <= limit; j++)
if (i % j == 0)
{
isPrime = false;
break;
}
if (isPrime)
{
yield return i;
}
}
}
And I want to update my list box without blocking my UI thread, with all the prime numbers using above code. The approch which I am using as following but this is not working out.
public MainWindow()
{
InitializeComponent();
_worker = new BackgroundWorker();
_worker.DoWork += _worker_DoWork;
this.DataContext = this;
}
private void _worker_DoWork(object sender, DoWorkEventArgs e)
{
PrimeNumbers = new ObservableCollection<int>();
foreach (var item in GetPrimes(1, 10000000))
{
Dispatcher.BeginInvoke(new Action<int>(Test), item);
}
}
private void Test(int obj)
{
PrimeNumbers.Add(obj);
}
public ObservableCollection<int> PrimeNumbers
{
get
{
return primeNumbers;
}
set
{
primeNumbers = value;
OnPropertyChanged("PrimeNumbers");
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
_worker.RunWorkerAsync();
}
but this approach freezes my UI. I want to have result continuously coming from the GetPrimes method and keep adding to my listboz
You are just posting too much. This code works as expected:
public partial class MainWindow
{
public ObservableCollection<int> PrimeNumbers { get; set; }
public MainWindow()
{
InitializeComponent();
PrimeNumbers = new ObservableCollection<int>();
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
PrintPrimes(PrimeNumbers.Add, 1, 10000000, SynchronizationContext.Current);
}
private static void PrintPrimes(Action<int> action, int from, int to,
SynchronizationContext syncContext)
{
Task.Run(() =>
{
for (var i = from; i <= to; i++)
{
var isPrime = true;
var limit = (int) Math.Sqrt(i);
for (var j = 2; j <= limit; j++)
{
if (i%j == 0)
{
isPrime = false;
break;
}
}
if (isPrime)
{
syncContext.Post(state => action((int)state), i);
Thread.Sleep(1);
}
}
});
}
}
Consider avoiding old BackgroundWorker class. Also, instead of using a synchronization mechanism of your platform try to switch to platform independent SynchronizationContext.
Instead of sleeping a thread you can post your results in bunches:
public partial class MainWindow
{
public ObservableCollection<int> PrimeNumbers { get; set; }
public MainWindow()
{
InitializeComponent();
PrimeNumbers = new ObservableCollection<int>();
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
PrintPrimes(items => items.ForEach(PrimeNumbers.Add),
1, 10000000, SynchronizationContext.Current);
}
private static void PrintPrimes(Action<List<int>> action, int from, int to,
SynchronizationContext syncContext)
{
Task.Run(() =>
{
var primesBuffer = new List<int>();
for (var i = from; i <= to; i++)
{
var isPrime = true;
var limit = (int) Math.Sqrt(i);
for (var j = 2; j <= limit; j++)
{
if (i%j == 0)
{
isPrime = false;
break;
}
}
if (isPrime)
{
primesBuffer.Add(i);
if (primesBuffer.Count >= 1000)
{
syncContext.Post(state => action((List<int>) state),
primesBuffer.ToList());
primesBuffer.Clear();
}
}
}
});
}
}
You can use Thread instead of Task.Run if you're stuck with older versions of a framework.
That code looks ok, you just forgot to start your background worker by calling BackgroundWorker.RunWorkerAsync.
PrimeNumbers = new ObservableCollection<int>();
This line needs to be outside your worker (or being invoked in the UI thread as well).
It seems like my UI was getting upadated very frequently so I have used a delay of 1 second on my background worker thread. It helped me to achieve my functionality
private void _worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var item in GetPrimes(1, 1000000))
{
Thread.Sleep(1000);
Dispatcher.BeginInvoke(new Action<int>(Test), item);
}
}
I have a CountDownTimer class that updates a controller that updates the user interface. The problem i have is when i run my unit tests i get a NllReferenceException because the event handlder(Tick) is never initialized always null. What is the best possible solution to this problem? Or should i go about it differently. Thanks
public class CountDownTimer : ICountDownTimer
{
private int seconds; // Time in seconds
private int reSetValue; // Time in seconds
private System.Windows.Forms.Timer timer1;
public event TickHandler Tick;
public EventArgs e = null;
public delegate void TickHandler(CountDownTimer m, EventArgs e, int seconds);
public CountDownTimer(int seconds)
{
this.seconds = seconds;
reSetValue = seconds;
timer1 = new System.Windows.Forms.Timer();
timer1.Tick += new EventHandler(timer1_Tick); // Add Handler(timer1_Tick)
timer1.Interval = 1000; // 1 second
}
private void timer1_Tick(object sender, EventArgs e)
{
CallTickHandler();
if (getSeconds() == 0) // Stop Timer at 0
{
timer1.Stop(); // Stop timer
}
else
{
if (getSeconds() % 60 == 0 || getSeconds() >= 1 && getSeconds() <= 10)
{
CallTickHandler();
}
}
seconds--; // Decrement seconds
}
public void StartTimer()
{
timer1.Start();
}
public void StopTimer()
{
timer1.Stop();
}
public void ResetTimer()
{
timer1.Stop();
seconds = reSetValue;
CallTickHandler();
}
public void SetTimer(int seconds)
{
timer1.Stop();
this.seconds = getSeconds();
reSetValue = getSeconds();
CallTickHandler();
}
internal void CallTickHandler()
{
Tick(this, e, getSeconds());
}
public Boolean isEnabled()
{
return timer1.Enabled;
}
public int getSeconds()
{
return seconds;
}
}
public class Controller : ApplicationContext
{
//Store a reference to the UI
internal frmMain MainUI { get; set; }
private int seconds = 300;
CountDownTimer timer;
public Controller()
{
MainUI = new frmMain(this);
//We can do any necessary checks or changes to the MainUI here before it becomes visible
MainUI.Show();
timer = new CountDownTimer(seconds);
SubscribeToTickListener(timer);
TickUpdate(seconds);
}
internal void TickUpdate(string seconds)
{
MainUI.lblTimer.Text = ("" + Convert.ToInt32(seconds) / 60).PadLeft(2, '0') + "m:" + ("" + Convert.ToInt32(seconds) % 60).PadLeft(2, '0') + "s";
}
internal void TickUpdate(int seconds)
{
MainUI.lblTimer.Text = ("" + seconds / 60).PadLeft(2, '0') + "m:" + ("" + seconds % 60).PadLeft(2, '0') + "s";
if (seconds <= 10)
{
//ss.Speak(seconds.ToString());
}
else
{
//ss.Speak((seconds / 60).ToString() + " minute warning");
}
}
internal void StartTimer()
{
timer.StartTimer();
}
internal void ResetTimer()
{
timer.ResetTimer();
}
internal void StopTimer()
{
timer.StopTimer();
}
internal void SetTimer(int seconds)
{
timer.SetTimer(seconds);
}
public void SubscribeToTickListener(CountDownTimer cdt)
{
cdt.Tick += new CountDownTimer.TickHandler(TickMsgRecieved);
}
public void TickMsgRecieved(CountDownTimer cdt, EventArgs e, int seconds)
{
TickUpdate(seconds);
TickUpdate(seconds.ToString());
}
}
public class CountDownTimerTests
{
private CountDownTimer t = new CountDownTimer(300);
[TestMethod()]
public void CountDownTimerTest()
{
CountDownTimer t = new CountDownTimer(300);
}
[TestMethod()]
public void StartTimerTest()
{
//CountDownTimer t = new CountDownTimer(300);
t.StartTimer();
Boolean expected = t.isEnabled();
Boolean actual = true;
Assert.AreEqual(expected, actual);
}
[TestMethod()]
public void StopTimerTest()
{
//CountDownTimer t = new CountDownTimer(300);
t.StartTimer();
t.StopTimer();
Boolean expected = t.isEnabled();
Boolean actual = false;
Assert.AreEqual(expected, actual);
}
[TestMethod()]
public void ResetTimerTest()
{
int expected = t.getSeconds();
t.ResetTimer();
int actual = t.getSeconds();
Assert.AreEqual(expected, actual);
}
[TestMethod()]
public void SetTimerTest()
{
int expected = t.getSeconds();
t.SetTimer(120);
int actual = t.getSeconds();
Assert.AreEqual(expected, actual);
}
}
In this case you can probably use a mock implementation of the event. I would add the following mock event to your test class to simulate a consumer of the CountDownTimer class.
[TestInitialize]
public void TestSetup()
{
t.Tick += new CountDownTimer.TickHandler(MockTickEvent);
}
[TestCleanup]
public void TestCleanup()
{
t.Tick -= MockTickEvent;
}
void MockTickEvent(CountDownTimer m, EventArgs e, int seconds)
{
///you may need to add further test code here to fully cover your code
return;
}
I have simple WindowsForm application, which contains TreeView. When initializing - TreeView is building from XML with default imageIndexes. My TreeView consists of servers names. In Tag element I put Dictionary which consists of a host and IP. After the initialization I call method, wich change the imageIndex if server is timedOut. I need call this method in BackgroundWorker that non block main thread (GUI). I plan to run this method every minute. Below is this method:
private void checkServersTree()
{
TreeNodeCollection rootNodes = treeViewSrv.Nodes;
TreeNodeCollection childNodes;
PingServers ps = new PingServers();
for (int i = 0; i < rootNodes.Count; i++)
{
childNodes = treeViewSrv.Nodes[i].Nodes;
treeViewSrv.Nodes[i].Text += string.Format(" ({0})", childNodes.Count);
int downServers = 0;
foreach (TreeNode tNode in childNodes)
{
if (tNode.Tag != null)
{
Dictionary<string, string> dicParams = tNode.Tag as Dictionary<string, string>;
if (!ps.getServerStatus(dicParams["host"], dicParams["ip"]))
{
tNode.ImageIndex = 1; //red
rootNodes[i].ImageIndex = 2; //yellow
downServers++;
}
}
}
if(downServers == childNodes.Count)
rootNodes[i].ImageIndex = 4; //fatal red
}
}
Thanks in advance!
Thanks for answer!
Before I write here I read about Invoke and BeginInvoke. But I can't solve this problem - run asynchronous thread that changing TreeView ImageIndexes. I went for your link and wrote code (below), but GUI stay blocked. What am I doing wrong?
namespace CCCServers
{
public partial class CCCServers : Form
{
public delegate void checkSrvDelegate();
public checkSrvDelegate myDelegate;
private Thread myThread;
public CCCServers()
{
InitializeComponent();
TreeFromXML tfXML = new TreeFromXML(treeViewSrv, "../../Servers.xml");
tfXML.initTreeNodesFromXML();
treeViewSrv.ExpandAll();
//checkServersTree();
myDelegate = new checkSrvDelegate(checkServersTree);
}
private void checkServersTree()
{
TreeNodeCollection rootNodes = treeViewSrv.Nodes;
TreeNodeCollection childNodes;
PingServers ps = new PingServers();
for (int i = 0; i < rootNodes.Count; i++)
{
childNodes = treeViewSrv.Nodes[i].Nodes;
treeViewSrv.Nodes[i].Text += string.Format(" ({0})", childNodes.Count);
int downServers = 0;
foreach (TreeNode tNode in childNodes)
{
if (tNode.Tag != null)
{
Dictionary<string, string> dicParams = tNode.Tag as Dictionary<string, string>;
if (!ps.getServerStatus(dicParams["host"], dicParams["ip"]))
{
tNode.ImageIndex = 1; //red
rootNodes[i].ImageIndex = 2; //yellow
downServers++;
}
}
}
if(downServers == childNodes.Count)
rootNodes[i].ImageIndex = 4; //fatal red
}
}
private void btnRDP_Click(object sender, EventArgs e)
{
myThread = new Thread(new ThreadStart(threadFunction));
myThread.Start();
}
private void threadFunction()
{
MyThreadClass myThreadClassObject = new MyThreadClass(this);
myThreadClassObject.Run();
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
MessageBox.Show(e.Node.Text, "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void treeViewSrv_AfterSelect(object sender, TreeViewEventArgs e)
{
e.Node.SelectedImageIndex = e.Node.ImageIndex; //Что бы иконка не менялась при выборе узла
}
}
//***************************************************************************
public class MyThreadClass
{
CCCServers cccSrv;
public MyThreadClass(CCCServers myForm)
{
cccSrv = myForm;
}
public void Run()
{
cccSrv.Invoke(cccSrv.myDelegate);
}
}
}
You need to use form's Invoke method to execute user interface methods on the thread that created window handles. E.g.
Invoke((Action) checkServersTree);
I solve my problem. After insert on my code in special places this Debug.WriteLine(String.Format("<<<<<<<< This is {0} thread", Thread.CurrentThread.ManagedThreadId)); I realized that the Control.Invoke is called in the main thread, and therefore all who take the time to realize not to the Control.Invoke. See the code below:
private delegate void setServersCountDelegate(int idx, int cnt);
private delegate void setChldNodeImgIndexDelegate(TreeNode tNode, int imgIdx);
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
testBuildTree();
}
private void testBuildTree()
{
Thread.CurrentThread.Name = "CheckServers";
Debug.WriteLine(String.Format("|||||||||| This is {0} thread", Thread.CurrentThread.ManagedThreadId));
changeImgIndex();
}
private void changeImgIndex()
{
Debug.WriteLine(String.Format("--------- This is {0} thread", Thread.CurrentThread.ManagedThreadId));
TreeNodeCollection rootNodes = treeViewSrv.Nodes;
TreeNodeCollection childNodes;
for (int i = 0; i < rootNodes.Count; i++)
{
childNodes = treeViewSrv.Nodes[i].Nodes;
//treeViewSrv.Nodes[i].Text += string.Format(" ({0})", childNodes.Count);
treeViewSrv.Invoke(new setServersCountDelegate(setServersCount), new object[] { i, childNodes.Count });
int downServers = 0;
foreach (TreeNode tNode in childNodes)
{
if (tNode.Tag != null)
{
Dictionary<string, string> dicParams = tNode.Tag as Dictionary<string, string>;
if (!getServerStatus(dicParams["host"], dicParams["ip"]))
{
Debug.WriteLine(String.Format("<<<<<<<< This is {0} thread", Thread.CurrentThread.ManagedThreadId));
//tNode.ImageIndex = 1; //red
treeViewSrv.Invoke(new setChldNodeImgIndexDelegate(setChldNodeImgIndex), new object[] { tNode, 1 }); //red
//rootNodes[i].ImageIndex = 2; //yellow
treeViewSrv.Invoke(new setChldNodeImgIndexDelegate(setChldNodeImgIndex), new object[] { rootNodes[i], 2 }); //yellow
downServers++;
}
}
}
if (downServers == childNodes.Count)
{
//rootNodes[i].ImageIndex = 4; //fatal red
treeViewSrv.Invoke(new setChldNodeImgIndexDelegate(setChldNodeImgIndex), new object[] { rootNodes[i], 4 });
}
}
}
private void setServersCount(int idx, int cnt)
{
string[] spltNodeText = treeViewSrv.Nodes[idx].Text.Split(' ');
treeViewSrv.Nodes[idx].Text = string.Format("{0} ({1})", spltNodeText[0], cnt);
}
private void setChldNodeImgIndex(TreeNode tNode, int imgIdx)
{
tNode.ImageIndex = imgIdx;
}
I'm trying to access information on a web browser from another thread. When trying to access the browser.DocumentTitle, I get this error:
The name DocumentTitle does not exist in the current context
I can successfully navigate to webpages inside the DoWork or ProcessWebPage methods but I cannot access the GetTitle function without crashing. I have been working on this part alone for days and simply cannot figure it out.
Here is the problem code:
BROWSER CODE
class BrowserInterface : Form
{
WebBrowser browser;
Thread thread;
State state;
public State State { get { return state; } }
public BrowserInterface()
{
Initialize();
}
void Initialize()
{
browser = new WebBrowser();
state = State.Null;
state = State.Initializing;
thread = new Thread(StartThread);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
while (state == State.Initializing) Thread.Sleep(20);
}
void StartThread()
{
browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Name = "webBrowser";
browser.ScrollBarsEnabled = false;
browser.TabIndex = 0;
browser.DocumentCompleted +=
new WebBrowserDocumentCompletedEventHandler(this.Web_Completed);
Form form = new Form();
form.Controls.Add(browser);
form.Name = "Browser";
state = State.Null;
Application.Run(form);
}
public void Navigate(string url)
{
state = State.Navigating;
if (browser.IsDisposed)
Initialize();
browser.Navigate(url);
}
public string GetTitle()
{
if (InvokeRequired)
{
BeginInvoke(new MethodInvoker(() => GetTitle()));
}
return browser.DocumentTitle;
}
private void Web_Completed(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var br = sender as WebBrowser;
if (br.Url == e.Url)
state = State.Completed;
}
}
enum State
{
Initializing,
Null,
Navigating,
Completed
}
OTHER THREAD
class Controller
{
public int ThreadsAllowed;
private ManualResetEvent[] resetEvent;
private BrowserInterface[] browser;
static Thread mainThread;
bool run;
bool exit;
public Controller(int threadsAllowed)
{
ThreadsAllowed = threadsAllowed;
resetEvent = new ManualResetEvent[ThreadsAllowed];
browser = new BrowserInterface[ThreadsAllowed];
for (int i = 0; i < ThreadsAllowed; i++)
{
resetEvent[i] = new ManualResetEvent(true);
browser[i] = new BrowserInterface();
}
ThreadPool.SetMaxThreads(ThreadsAllowed, ThreadsAllowed);
mainThread = new Thread(RunThread);
mainThread.Start();
run = false;
exit = false;
}
public void Run()
{
run = true;
}
void RunThread()
{
while (true)
{
while (!run) Thread.Sleep(20);
while (mode == ScoutMode.Off) Thread.Sleep(100);
//wait for the last set to complete
WaitHandle.WaitAll(resetEvent);
if (exit)
break;
for (int i = 0; i < ThreadsAllowed; i++)
ThreadPool.QueueUserWorkItem(DoWork, i);
}
}
void DoWork(object o)
{
int i = (int)o;
if(browser[i].state == State.null)
{
…
… navigation code that works …
…
return;
}
else if(browser[i].state == State.Completed)
ProcessWebPage(i);
}
void ProcessWebPage(int i)
{
string title;
try
{
title = browser[i].GetTitle();
}
catch { return; }
}
}
What hurts my eye is your GetTitle function. When using MethodInvoker, you're dealing with methods of void type, that is, you cannot get return value from the function. That's why you need a delegate which will return you the value.
Also, you have to have else statement, so to not try to return the value when invoking is in fact required.
class BrowserInterface : Form
{
/* ... */
private delegate string StringDelegate();
public string GetTitle()
{
/*
if (InvokeRequired)
{
BeginInvoke(new MethodInvoker(() => GetTitle()));
}
return browser.DocumentTitle;
*/
if (InvokeRequired)
{
object result = Invoke(new StringDelegate(GetTitle));
return (string)result;
}
else
return browser.DocumentTitle;
}
/* ... */
}
At first, use browsers invoke instead of forms one. And the main problem that after invokation you will return to code and try to access browser.DocumentTitle as background thread. To avoid this, add else construction.
public string GetTitle()
{
if (this.browser.InvokeRequired)
{
this.browser.Invoke(new MethodInvoker(() => GetTitle()));
}
else
{
return browser.DocumentTitle;
}
}