I have my main GUI thread, and a second thread running inside it\'s own ApplicationContext (to keep it alive, even when there is no work to be done). I want to call a method
I'm assuming some event in the GUI requires some long-running task to start which be run in the background - there are two main ways to do this. If you simply want to call a method on a different thread then you can do it by Calling Synchronous Methods Asynchronously. I usually do something like this:
//delegate with same prototype as the method to call asynchrously
delegate void ProcessItemDelegate(object item);
//method to call asynchronously
private void ProcessItem(object item) { ... }
//method in the GUI thread
private void DoWork(object itemToProcess)
{
//create delegate to call asynchronously...
ProcessItemDelegate d = new ProcessItemDelegate(this.ProcessItem);
IAsyncResult result = d.BeginInvoke(itemToProcess,
new AsyncCallback(this.CallBackMethod),
d);
}
//method called when the async operation has completed
private void CallbackMethod(IAsyncResult ar)
{
ProcessItemDelegate d = (ProcessItemDelegate)ar.AsyncState;
//EndInvoke must be called on any delegate called asynchronously!
d.EndInvoke(ar);
}
Be aware when using this method that the callback is executed on the background thread, so any updates to the GUI must be done using Invoke.
Alternatively you could use shared state to communicate between threads and use an EventWaitHandle to signal updates to the shared state - in this example a method in the GUI adds work items to a queue to be handled in the background. The worker thread processes items from the queue when work becomes available.
//shared state
private Queue workQueue;
private EventWaitHandle eventHandle;
//method running in gui thread
private void DoWork(Item itemToProcess)
{
//use a private lock object instead of lock...
lock(this.workQueue)
{
this.workQueue.Add(itemToProcess);
this.eventHandle.Set();
}
}
//method that runs on the background thread
private void QueueMonitor()
{
while(keepRunning)
{
//if the event handle is not signalled the processing thread will sleep here until it is signalled or the timeout expires
if(this.eventHandle.WaitOne(optionalTimeout))
{
lock(this.workQueue)
{
while(this.workQueue.Count > 0)
{
Item itemToProcess = this.workQueue.Dequeue();
//do something with item...
}
}
//reset wait handle - note that AutoResetEvent resets automatically
this.eventHandle.Reset();
}
}
}
Put a loop in your second thread, that sleeps most of the time, but every [Interval] it wakes up and checks a shared variable that tells it whether to run your method or not, and if that shared boolean is set to true, then it runs a method that performs whatever task you are trying to perform... In that method, have the method gather the data required from another shared variable.
In main GUI thread, put the data into the method parameter shared variable, then set the boolean "Run" shared variable to true...
Inside the worker method, remember to reset the shared bool "run" variable to false when you're done, so the loop won;t run the same instance over and over...
Use a synchronization object to signal the thread that it needs to process the new data (or the GUI's new state). One relatively simple way to do this is to use an event object. Here's a run-down of how that would work:
.Net already comes with a System.ComponentModel.BackgroundWorker
class specifically to handle performing background tasks and communicating with a GUI. Use it.
In effect, you have created a poor man's version of a ThreadPool. Your second thread is just sitting there doing nothing and without a fair amount of work on your part, you can't just get it to do work for you. You would have to pass delegates into a queue that your thread then takes off and executes.
Your best bet is to do what you intended and just use the .NET ThreadPool and give it work to do.
The convenience of Control.BeginInvoke() is hard to pass up. You don't have to. Add a new class to your project and paste this code:
using System;
using System.Threading;
using System.Windows.Forms;
public partial class frmWorker : Form {
public frmWorker() {
// Start the worker thread
Thread t = new Thread(new ParameterizedThreadStart(WorkerThread));
t.IsBackground = true;
t.Start(this);
}
public void Stop() {
// Synchronous thread stop
this.Invoke(new MethodInvoker(stopWorker), null);
}
private void stopWorker() {
this.Close();
}
private static void WorkerThread(object frm) {
// Start the message loop
frmWorker f = frm as frmWorker;
f.CreateHandle();
Application.Run(f);
}
protected override void SetVisibleCore(bool value) {
// Shouldn't become visible
value = false;
base.SetVisibleCore(value);
}
}
Here's some sample code to test it:
public partial class Form1 : Form {
private frmWorker mWorker;
public Form1() {
InitializeComponent();
mWorker = new frmWorker();
}
private void button1_Click(object sender, EventArgs e) {
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
mWorker.BeginInvoke(new MethodInvoker(RunThisOnThread));
}
private void RunThisOnThread() {
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
}
private void button2_Click(object sender, EventArgs e) {
mWorker.Stop();
}
}