Using SmartThread with STA and event driven enviroment - c#

Hi I have a use case where I am supposed to use C# Web Browser inside a form and get some data from it. However as we know the C# browser runs under STA hence I have created an instance of SmartThread with single thread architecture.
On some events from an MVC app I am supposed to invoke the web browser and get the result. When the browser is instantiated at that moment I can invoke any JavaScript method, however when I wait for an event in MVC and invoke on event the SmartThread doesn't seems to invoke the webbrowser.
Here is my below code please help me.
public class FormBrowser
{
public static Form1 form1 = null;
public static SmartThreadPool smartThreadPool;
public static void initialise()
{
if (smartThreadPool == null)
{
STPStartInfo stpStartInfo = new STPStartInfo();
stpStartInfo.StartSuspended = true;
stpStartInfo.ApartmentState = ApartmentState.STA;
stpStartInfo.MaxWorkerThreads = 1;
smartThreadPool = new SmartThreadPool(stpStartInfo);
var ab = smartThreadPool.QueueWorkItem(new WorkItemCallback(initForm), "line");
smartThreadPool.Start();
//smartThreadPool.Shutdown();
}
}
public static object initForm(object msg)
{
var id = Thread.CurrentThread.ManagedThreadId;
form1 = new Form1();
System.Windows.Forms.Application.Run(form1);
return null;
}
public static void redraw()
{
var id = Thread.CurrentThread.ManagedThreadId;
smartThreadPool.QueueWorkItem(new WorkItemCallback(rd), "line");
}
public static object rd(object msg)
{
var id = Thread.CurrentThread.ManagedThreadId;
form1.webBrowser1.Document.InvokeScript("startDrawing");
return null;
}
}
On calling initialise() method SmartThread will create a thread pool and then creates a Form instance which holds a Web Browser. Next when any event occures from client say he request for a latest data I have to call method redraw() and which should internally call rd() but that never happens.
Please help me to fix this, if I am missing something let me know.

The below code worked for me.
public class FormBrowser
{
public static Form1 form1 = null;
public static SmartThreadPool smartThreadPool;
public static SynchronizationContext ctx;
public static string html;
public static void initialise()
{
if (smartThreadPool == null)
{
STPStartInfo stpStartInfo = new STPStartInfo();
stpStartInfo.StartSuspended = false;
stpStartInfo.ApartmentState = ApartmentState.STA;
stpStartInfo.MaxWorkerThreads = 1;
smartThreadPool = new SmartThreadPool(stpStartInfo);
var ab = smartThreadPool.QueueWorkItem(new WorkItemCallback(initForm), "line");
smartThreadPool.Start();
//smartThreadPool.Shutdown();
}
}
public static object initForm(object msg)
{
var id = Thread.CurrentThread.ManagedThreadId;
form1 = new Form1();
ctx = WindowsFormsSynchronizationContext.Current;
System.Windows.Forms.Application.Run(form1);
return null;
}
public static string redraw(string chartType)
{
ctx.Send(rd, chartType);
return html;
}
public static void rd(object chartType)
{
var id = Thread.CurrentThread.ManagedThreadId;
html = (string)form1.webBrowser1.Document.InvokeScript("drawChart", new object[] { chartType });
}
}
If SmartThreadPool used along with the SynchronizationContext works well. However using SynchronizationContext alone or SmartThreadPool alone won't solve the issue.

Related

C# - Edit Form Label using Static Method

public static void Monitor0()
{
bool ToMineOrNot = Backend.ToMineOrNot;
while (ToMineOrNot)
{
Form1 temp = new Form1();
Form1.NonStaticDelegate = new Action(temp.setHashRate);
Form1.NonStaticDelegate();
Backend.hps = 0;
Thread.Sleep(1000);
}
if (ToMineOrNot == false)
{
}
}
public void setHashRate()
{
hashrate.Text = Backend.hps.ToString();
}
I have to get the Static Void to call the non Static Void, i have to have Monitor0 Static because it has to be Run in a Thread, and setHashRate() has to be Non-static to edit the label (It's a Windows form):
Thread thread = new Thread(Monitor0);
thread.Start();
Does anybody know how i could Do that? and I cant just do this because the label won't be changed:
Form1 form = new Form();
form.label1.Text = "text"

How to run in main thread from worker thread that start by Nancyfx? C#

I have a problem with using nancyfx in my winform application (I make a winform app and use a nancyfx inside the application) So i can use some API url to make change in the winform without additional server or services (because i attached the nancy in the winform apps)
Here is my Form1.cs
public partial class Form1 : Form
{
public Form1(bool test)
{
InitializeComponent();
textBox1.Text += "Apps Method "+ Environment.NewLine;
}
public bool startTestAPI()
{
textBox1.Text += "Api Worked" + Environment.NewLine);
}
private void button2_Click(object sender, EventArgs e)
{
HostingAPI s = new HostingAPI();
s.Start();
textBox1.Text += "Api Running" + Environment.NewLine);
}
}
public class ModuleCDM : NancyModule
{
public ModuleCDM()
{
try
{
Thread th2 = Thread.CurrentThread;
Get["/Start"] = parameters =>
{
Form1 form = new Form1(false);
Thread testthread = Form1.curthread;
bool res = form.startTestAPI();
if (res == true)
{
var feeds = new string[] { "Success" };
return Response.AsJson(feeds);
}
else
{
var feeds = new string[] { "Failed" };
return Response.AsJson(feeds);
}
};
}
}
}
and this is my HostingAPI.cs
public class HostingAPI
{
private NancyHost hostNancy;
private string hostUrl;
public void Start()
{
hostUrl = ConfigModule.ModuleAddress;
if (hostUrl == null) hostUrl = "http://localhost:5005";
hostNancy = new NancyHost(new Uri(hostUrl));
hostNancy.Start();
}
public void Stop()
{
hostNancy.Stop();
}
}
And it successfully run without error, but when i call api (localhost:5005/Start) the textbox in winform apps not add the text i wanted ("Api Worked"). I noticed it is because Nancyfx create another thread when there is API call, and i can use invoke/begininvoke because !invokerequired always comes with value false. So how can i access the main thread or maybe another solution to update the UI when i call the API.
Thanks
You have 2 issues in here.
You start host api service from Form1 instance then within Nancy Module you create a different Form1 instance which is invisible and you try to do access certain methods within that class
Cross thread issue as you rightfully guessed . You are trying to write from another thread context than the UI thread
Look at the code at below to achieve this. Bear in mind that you can create Singleton Form or find another way to access the instance of Form1
public class HostingAPI
{
private NancyHost hostNancy;
private string hostUrl;
public HostingAPI()
{
}
public void Start()
{
var hostConfig = new HostConfiguration
{
UrlReservations = new UrlReservations
{
CreateAutomatically = true
},
};
//hostUrl = ConfigModule.ModuleAddress;
if (hostUrl == null) hostUrl = "http://localhost:5005";
hostNancy = new NancyHost(hostConfig,new Uri(hostUrl));
hostNancy.Start();
}
public void Stop()
{
hostNancy.Stop();
}
}
public partial class Form1 : Form
{
delegate void SetTextCallback(string text);
public static Form1 Instance;
public Form1(bool test)
{
InitializeComponent();
textBox1.Text += "Apps Method " + Environment.NewLine;
Instance = this;
}
private void button1_Click(object sender, EventArgs e)
{
HostingAPI s = new HostingAPI();
s.Start();
textBox1.Text += "Api Running" + Environment.NewLine;
}
public void startTestAPI()
{
SetText("Api Worked" + Environment.NewLine);
}
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text += text;
}
}
}
public class ModuleCDM : NancyModule
{
public ModuleCDM()
{
try
{
Thread th2 = Thread.CurrentThread;
Get["/Start"] = parameters =>
{
var form1 = Form1.Instance;
form1.startTestAPI();
var feeds = new[] {"Success"};
return Response.AsJson(feeds);
};
}
catch
{
}
}
}

Handle a message I just sent with ActiveMQ and C#

I'm a beginner at using ActiveMQ with C#. I've created a simple windows form with one button and one label. When I click on the button, i send a message to the queue and the label is initialized with the message I just sent. Of course, I could initialize my label directly but I want my form to rather consume the message from the queue in order to update my label.
The problem is I don't manage to handle the message in the same form to update my label. My consumer code is not called at all and yet, its initialized in the Load event of my form.
Here's the code
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
InitializeHandlerAMQ();
}
private void InitializeHandlerAMQ()
{
Tchat tchat = null;
IDestination dest = _session.GetQueue(QUEUE_DESTINATION);
using(IMessageConsumer consumer = _session.CreateConsumer(dest))
{
IMessage message;
while((message = consumer.Receive(TimeSpan.FromMilliseconds(2000))) != null)
{
var objectMessage = message as IObjectMessage;
if(objectMessage != null)
{
tchat = objectMessage.Body as Tchat;
if (tchat != null)
{
textBox2.Text += string.Format("{0}{1}", tchat.Message, Environment.NewLine);
}
}
}
}
}
If I close my windows form and restart it, then my label is well updated but I don't want to close it and re open it.
Do you have any ideas guys ?
Try creating a class with an event delegate like this.
A subscriber class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apache.NMS;
using Apache.NMS.ActiveMQ;
using Apache.NMS.ActiveMQ.Commands;
namespace Utilities
{
public delegate void QMessageReceivedDelegate(string message);
public class MyQueueSubscriber : IDisposable
{
private readonly string topicName = null;
private readonly IConnectionFactory connectionFactory;
private readonly IConnection connection;
private readonly ISession session;
private readonly IMessageConsumer consumer;
private bool isDisposed = false;
public event QMessageReceivedDelegate OnMessageReceived;
public MyQueueSubscriber(string queueName, string brokerUri, string clientId)
{
this.topicName = queueName;
this.connectionFactory = new ConnectionFactory(brokerUri);
this.connection = this.connectionFactory.CreateConnection();
this.connection.ClientId = clientId;
this.connection.Start();
this.session = connection.CreateSession();
ActiveMQQueue topic = new ActiveMQQueue(queueName);
//this.consumer = this.session.CreateDurableConsumer(topic, consumerId, "2 > 1", false);
this.consumer = this.session.CreateConsumer(topic, "2 > 1");
this.consumer.Listener += new MessageListener(OnMessage);
}
public void OnMessage(IMessage message)
{
ITextMessage textMessage = message as ITextMessage;
if (this.OnMessageReceived != null)
{
this.OnMessageReceived(textMessage.Text);
}
}
#region IDisposable Members
public void Dispose()
{
if (!this.isDisposed)
{
this.consumer.Dispose();
this.session.Dispose();
this.connection.Dispose();
this.isDisposed = true;
}
}
#endregion
}
}
Winforms
In your windows form Subscribe to the queue like this
MyQueueSubscriber QueueSubscriber = new MyQueueSubscriber(QueueName, ActiveMQHost, QueueClientId);
QueueSubscriber.OnMessageReceived += new QMessageReceivedDelegate(QueueSubscriber_OnMessageReceived);
static void QueueSubscriber_OnMessageReceived(string message)
{
SetText(message);
}
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.labelname.value = text;
}
}
Resources:
Unfortunately there are not that many resources to teach C# & ActiveMQ. Try using http://activemq.apache.org/nms/ as this was quite good.
Try looking at a small article from http://www.codersource.net/MicrosoftNet/CAdvanced/PublishSubscribeinCusingActiveMQ.aspx. Disclaimer: This is my website and the article was written by me. Sorry for the self publicity. But I feel this is relevant to the topic.

Cross-thread operation not valid: Asynchronous delegates error

I've been trying to learn delegates.I just created a button,label and checkbox. If I click checkbox, the time format changes. If i click the button , i print the date accordingly. However when trying to use asynchromous delegate i.e., to use another thread, i am stuck with an error
public delegate void AsyncDelegate(bool seconds);
public partial class Form1 : Form
{
AsyncDelegate ad;
TimeZ t = new TimeZ();
public Form1()
{
InitializeComponent();
}
private void btn_async_Click(object sender, EventArgs e)
{
ad = new AsyncDelegate(t.GetTime);
AsyncCallback acb = new AsyncCallback(CB);
if (chk_sec.Checked)
{
ad.BeginInvoke(true, acb, null);
}
else
ad.BeginInvoke(false, acb, null);
}
public void CB(IAsyncResult ar)
{
t.Tim = ar.ToString();
ad.EndInvoke(ar);
lbl_time.Text = t.Tim;
}
and in another class library i get Timez used above. I add a reference of it in the project
public class TimeZ
{
private string tim;
public string Tim
{
get
{
return tim;
}
set
{
tim = value;
}
}
public string GetTime(bool seconds)
{
if (seconds)
{
return DateTime.Now.ToLongTimeString();
}
else
return DateTime.Now.ToShortTimeString();
}
}
However i get this error when i run the program:
Cross-thread operation not valid: Control 'lbl_time' accessed from a thread other than
the thread it was created on.
Can u help me out on how to solve this?
You cannot access forms and controls properties and methods from a thread that is not the form thread.
In windows, each window is bound to the thread that created it.
You can do that only with Control.BeginInvoke or the more useful System.Threading.SynchronizationContext class.
See http://msdn.microsoft.com/it-it/library/system.threading.synchronizationcontext(v=vs.95).aspx
See http://msdn.microsoft.com/it-it/library/0b1bf3y3(v=vs.80).aspx
It means, you have to post through synchronization context for example another async delegate in form thread.
public partial class Form1 : Form
{
AsyncDelegate ad;
TimeZ t = new TimeZ();
// Our synchronization context
SynchronizationContext syncContext;
public Form1()
{
InitializeComponent();
// Initialize the synchronization context field
syncContext = SynchronizationContext.Current;
}
private void btn_async_Click(object sender, EventArgs e)
{
ad = new AsyncDelegate(t.GetTime);
AsyncCallback acb = new AsyncCallback(CB);
if (chk_sec.Checked)
{
ad.BeginInvoke(true, acb, null);
}
else
{
ad.BeginInvoke(false, acb, null);
}
}
public void CB(IAsyncResult ar)
{
// this will be executed in another thread
t.Tim = ar.ToString(); // ar.ToString()???? this will not give you the time for sure! why?
ad.EndInvoke(ar);
syncContext.Post(delegate(object state)
{
// This will be executed again in form thread
lbl_time.Text = t.Tim;
}, null);
}
I don't know why you need an asynchronous callback to print time however :) really don't know why, thinking it is just some test code.

Dynamic method inovcation in new thread

Can some give me hand here? I need this method to invoke the user's desired method in a new thread based on the user's choice from the main form. I think i am close, but I am stuck at how to pass in a variable the represents that user's choice of method in the new thread start.
heres my code
public void GetTest()
{
_t = testListBox.SelectedIndex;
if (_t < 0)
return;
_v = testListBox.SelectedValue;
method = _v as MethodInfo;
if (method == null)
return;
_selectedMethod = method.Name;
MessageBox.Show(_selectedMethod.ToString());
counter++;
Thread UIthread = new Thread(new ThreadStart(??????)); // this is will be a method based on the user's choice in the main form thread
// adding Name of new Thread
UIthread.Name = "UIThread";
UIthread.Start();
// Update test status
_testStatus = "Running";
//Make thread global
_UIthread = UIthread;
}
How about something like this.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LoadList();
}
private void LoadList()
{
lstTest.Items.Clear();
var t1 = new MethodInfo
{
MethodName = "Test1",
MethodToRun = this.Test1
};
lstTest.Items.Add(t1);
var t2 = new MethodInfo
{
MethodName = "Test2",
MethodToRun = this.Test2
};
lstTest.Items.Add(t2);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
var t = lstTest.SelectedItem as MethodInfo;
if (t != null)
{
var a = new Task(t.MethodToRun);
a.Start();
}
}
private void Test1()
{
MessageBox.Show("Test1 started");
}
private void Test2()
{
MessageBox.Show("Test2 started");
}
}
public class MethodInfo
{
public string MethodName;
public Action MethodToRun;
public override string ToString()
{
return MethodName;
}
}
You can execute a lamdba expression on your new thread.
I'm not sure if you are dealing with static or instance methods. If your methods are static then you don't need to create an object instance:
method = _v as MethodInfo;
object[] parameters = null;
Thread UIthread = new Thread(() =>
{
object obj = null;
if (!method.IsStatic)
{
obj = Activator.CreateInstance(method.DeclaringType);
}
method.Invoke(obj, parameters);
});
UIthread.Start();
Thanks to everyone for all the help. Here's the answer for all the folks that care. You Create a thread start delegate. I found this out after reading these two posts: http://bytes.com/topic/net/answers/410294-using-reflection-multithreading http://www.pelennorfields.com/matt/2009/03/13/createdelegate-error-binding-to-target-method/
This method takes a reflected MethodInfo GetMethod listbox value and invokes the corresponding Instance method in a new thread. The thread is made global to allow the user to control the long-running method. The method is great because it allows for a dynamic class of instance methods, with out recompiling the entire program.code here
public void GetTest()
{
_t = testListBox.SelectedIndex;
if (_t < 0)
return;
_v = testListBox.SelectedValue;
method = _v as MethodInfo;
if (method == null)
return;
_selectedMethod = method.Name;
MessageBox.Show(_selectedMethod.ToString());
counter++;
ThreadStart ts = (ThreadStart)Delegate.CreateDelegate(typeof(ThreadStart), this, method, true);
Thread UIthread = new Thread(ts);
UIthread.Start();
_UIthread = UIthread;
}

Categories

Resources