I have a WinForm with a backgroundWorker:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SeoTools.Utils;
namespace SeoTools.UI
{
public partial class UIProgress : Form
{
public UIProgress(DoWorkEventHandler doWorkEventHandler, RunWorkerCompletedEventHandler runWorkerCompletedEventHandler)
{
InitializeComponent();
this.backgroundWorker.WorkerReportsProgress = true;
this.backgroundWorker.WorkerSupportsCancellation = true;
this.backgroundWorker.DoWork += doWorkEventHandler;
this.backgroundWorker.RunWorkerCompleted += runWorkerCompletedEventHandler;
}
public void Start()
{
var foo = SynchronizationContext.Current;
backgroundWorker.RunWorkerAsync();
}
private void btnStop_Click(object sender, EventArgs e)
{
btnStop.Enabled = false;
btnStop.Text = "Stopping...";
backgroundWorker.CancelAsync();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
try
{
wdgProgressBar.Value = e.ProgressPercentage;
if (this.Visible == false)
{
this.ShowDialog();
this.Update();
}
}
catch (InvalidOperationException) {}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.Hide(); //Here I get a InvalidOperationException
this.Dispose();
}
}
}
First time I run this it works fine. But second time I get InvalidOperationException when calling this.Hide().
"Additional information: Cross-thread operation not valid: Control 'UIProgress' accessed from a thread other than the thread it was created on."
The weird thing is on first run foo in Start() is a WindowsFormsSyncronizationContext but on the second try it's a System.Threading.SyncronizationContext.
The application I'm writing is a ExcelDna plugin.
EDIT
Start() is called like this:
UIProgress uiProgress = new UIProgress(
delegate(object sender, DoWorkEventArgs args)
{
....
},
delegate(object sender, RunWorkerCompletedEventArgs args)
{
...
}
);
uiProgress.Start();
Your Start() method must be called from code that runs on the UI thread to allow the BackgroundWorker to operate correctly. It was not when you get this exception. Add protective code to your method so you can diagnose this mishap:
public void Start()
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) {
throw new InvalidOperationException("Bug! Code called from a worker thread");
}
backgroundWorker.RunWorkerAsync();
}
Now you can set a breakpoint on the throw statement and use the debugger's Call Stack window to find out why this happened.
You are calling UI operation on background thread. This is the reason for that exception. I would use entirely different method to make the progress form the best one is to use Task with IProgress. The other way it to use this:
private void backgroundWorker_ProgressChanged( object sender , ProgressChangedEventArgs e )
{
this.UpdateOnMainThread(
( ) =>
{
wdgProgressBar.Value = e.ProgressPercentage;
if ( this.Visible == false )
{
this.ShowDialog( );
this.Update( );
}
} );
}
private void UpdateOnMainThread( Action action )
{
if ( this.InvokeRequired )
{
this.BeginInvoke( ( MethodInvoker ) action.Invoke);
}
else
{
action.Invoke( );
}
}
private void backgroundWorker_RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e )
{
this.UpdateOnMainThread(
( ) =>
{
this.Hide( ); //Here I get a InvalidOperationException
this.Dispose( );
} );
}
Use the BeginInvoke() method on the form:
//http://msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.110).aspx
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.BeginInvoke(new InvokeDelegate(InvokeMethod));
}
public delegate void InvokeDelegate();
public void InvokeMethod()
{
this.Hide();
this.Dispose();
}
I think you can find some help here: BackgroundWorker hide form window upon completion .
However don't forget to detach BackgroundWorker events and stop BackgroundWorker it self like explained in here: Proper way to Dispose of a BackGroundWorker .
The problem can be in the
this.Dispose();
in the backgroundWorker_RunWorkerCompleted event. With that you are disposing form page. Is that what you want to do? Or you want to dispose BackgroundWorker? Disposing form page all resources are released so doing this.Hide(); a second time can be a mistake.
For more info, you can see this links: C# Form.Close vs Form.Dispose and Form.Dispose Method
You must check this link
How to update the GUI from another thread in C#?
Probably have all the possible answers
Hope this Help
You're running a call to the main thread from a thread that can't manipulate UI.
The simplest way is to use anonymous delegate invoke.
Change this:
if (this.Visible == false)
{
this.ShowDialog();
this.Update();
}
For this:
this.Invoke((MethodInvoker) delegate {
if (this.Visible == false)
{
this.ShowDialog();
this.Update();
}
});
It's not the most optimized way but does the job awesomely fast without much recode. :)
Related
I'm new to c# and have a simple project where I have one Form1, which has a button to open Form2 (Diagnostics page to be password protected later on).
I have created a SerialPortClass which as you can guess handles all the serial port methods, such as openport, sendline, isPortOpen etc.
What I want to do is receive a serial string from the serial port in the SerialPortClass, then display this string in a text box in Form2. I have tried to achieve this in several ways after reading many posts on this site and others.
From what I read, using the BackGroundWorker is the best way of doing this. So I copied the example Microsoft Thread safe example, and have a button on Form2 to use the BackGroundWorker, to display text in the TextBox successfully. However when I try to run the BackGroundWorker from SerialPortClass I get a:
Exception thrown: 'System.NullReferenceException' in SerialTest.exe
Additional information: Object reference not set to an instance of an object.
Can anyone point me in the right direction please?
I know I'm actually passing the string yet, but just trying to start the background working in the other class as a test
Full SerialPortClass:
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
using System.Collections;
using System.Threading;
using System.Reflection;
using UsbLibrary;
namespace SerialTest
{
public class SerialPortClass : Form
{
private static SerialPortClass instance;
private System.IO.Ports.SerialPort serialPort1 = new SerialPort(); // Initilises an instance of COM port
private System.IO.Ports.SerialPort serialPort2 = new SerialPort(); // and another
Form1 form1;
Form2 form2;
internal delegate void SerialDataReceivedEventHandlerDelegate(
object sender, SerialDataReceivedEventArgs e);
delegate void SetTextCallback(string text);
string InputData = String.Empty;
private static SerialPort port;
public SerialPortClass()
{
serialPort1.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
public static readonly SerialPortClass _instance = new SerialPortClass();
public ThreadStart ThreadProcSafe { get; private set; }
public bool serialOpen(int port)
{
if (port == 1)
{
if (serialPort1.IsOpen) return true;
else return false;
}
else if (port == 2)
{
if (serialPort2.IsOpen) return true;
else return false;
}
else return false;
}
public void serialSendString(int port, string command)
{
if (port == 1)
{
// If the port is closed, don't try to send a character.
if (!serialPort1.IsOpen) return;
serialPort1.WriteLine(command);
}
else if (port == 2)
{
if (!serialPort2.IsOpen) return;
serialPort2.WriteLine(command);
}
else
{
MessageBox.Show("Invalid port no");
}
}
public void serialSendString1(string command)
{
// If the port is closed, don't try to send a character.
if (serialPort1.IsOpen)
{
serialPort1.WriteLine(command);
}
else
{
MessageBox.Show("port not opening at connect..");
return;
}
}
public void serialSendString2(string command)
{
// If the port is closed, don't try to send a character.
if (serialPort2.IsOpen)
{
serialPort2.WriteLine(command);
}
else
{
MessageBox.Show("port not opening at connect..");
return;
}
}
public void Connect() //SerialTest.Form1 form) //string comPortNo, int baud)
{
serialPort1.PortName = "COM38"; // comPortNo;
serialPort1.BaudRate = 9600; // baud;
if (serialOpen(1))
{
MessageBox.Show("Serial port already open");
return;
}
try
{
serialPort1.Open();
serialPort1.NewLine = "\r";
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
if (serialOpen(1))
{
Console.WriteLine("port open");
}
else
{
MessageBox.Show("port not opening at connect..");
}
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
Console.WriteLine("Data recieved");
InputData = serialPort1.ReadExisting();
if (InputData != String.Empty)
{
SetText(InputData);
}
}
public void SetText(string text)
{
Console.WriteLine("set text");
form2.backgroundWorker1.RunWorkerAsync();
}
public void disconnect()
{
//serialPort1.PortName = "COM38";
//serialPort1.BaudRate = 9600;
if (serialOpen(1))
{
serialPort1.Close();
Console.WriteLine("Port closed");
}
else
{
MessageBox.Show("Port not open to close");
}
}
public class SerialErrorReceivedEventArgs : EventArgs
{
//Data to pass to the event
public string LineData { get; private set; }
public SerialErrorReceivedEventArgs(string lineData)
{
this.LineData = lineData;
}
}
}
}
and Form2:
using System;
using System.Threading;
using UsbLibrary;
using log4net;
using SensorTestApp;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SerialTest;
using System.IO.Ports;
namespace SerialTest
{
public partial class Form2 : Form
{
string comPortNo;
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void SetTextCallback(string text);
// This thread is used to demonstrate both thread-safe and
// unsafe ways to call a Windows Forms control.
public Thread demoThread = null;
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
public BackgroundWorker backgroundWorker1;
//private TextBox tBQuery;
private Button setTextUnsafeBtn;
private Button setTextSafeBtn;
private Button setTextBackgroundWorkerBtn;
private System.ComponentModel.IContainer components1 = null;
public static Form2 _instance = new Form2();
public Form2()
{
InitializeComponent();
this.backgroundWorker1 = new BackgroundWorker();
// here you have also to implement the necessary events
// this event will define what the worker is actually supposed to do
this.backgroundWorker1.DoWork += backgroundWorker1_DoWork;
//this.backgroundWorker1.RunWorkerAsync += backgroundWorker1_RunWorkerAsync;
// this event will define what the worker will do when finished
this.backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
btnRelay1On.Enabled = false;
btnRelay2On.Enabled = false;
btnRelay3On.Enabled = false;
btnRelay4On.Enabled = false;
btnRelay5On.Enabled = false;
btnRelay1Off.Enabled = false;
btnRelay2Off.Enabled = false;
btnRelay3Off.Enabled = false;
btnRelay4Off.Enabled = false;
btnRelay5Off.Enabled = false;
}
// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private void button2_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
public void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
public void AppendText(String text)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<string>(AppendText), new object[] { text });
return;
}
this.richTextBox1.Text += text;
}
public 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.tBQuery.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
Console.WriteLine("different thread, text callback");
}
else
{
Console.WriteLine("same thread, string: %s", text);
this.tBQuery.Text = text;
}
}
// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
public void button1_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
public void backgroundWorker1_DoWork(object sender,
DoWorkEventArgs e)
{
Console.WriteLine("BackgroundWorker1_Do Work");
}
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous
// operations.
public void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.tBQuery.Text =
"This text was set safely by BackgroundWorker.";
}
private void cbComPort_SelectedIndexChanged(object sender, EventArgs e)
{
comPortNo = cbComPort.Text.ToString();
btnOpenCom.Enabled = true;
}
private void btnOpenCom_Click(object sender, EventArgs e)
{
//SerialPortClass.GetInstance().Connect(comPortNo, 9600);
try
{
SerialPortClass._instance.Connect();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
if (SerialPortClass._instance.serialOpen(1))
{
btnOpenCom.Enabled = false;
btnCloseCom.Enabled = true;
btnRelay1On.Enabled = true;
btnRelay2On.Enabled = true;
btnRelay3On.Enabled = true;
btnRelay4On.Enabled = true;
btnRelay5On.Enabled = true;
btnRelay1Off.Enabled = true;
btnRelay2Off.Enabled = true;
btnRelay3Off.Enabled = true;
btnRelay4Off.Enabled = true;
btnRelay5Off.Enabled = true;
}
else MessageBox.Show("port not open btnOpenCom");
}
private void btnCloseCom_Click(object sender, EventArgs e)
{
try
{
SerialPortClass._instance.disconnect();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
if (!SerialPortClass._instance.serialOpen(1))
{
btnOpenCom.Enabled = true;
btnCloseCom.Enabled = false;
btnRelay1On.Enabled = false;
btnRelay2On.Enabled = false;
btnRelay3On.Enabled = false;
btnRelay4On.Enabled = false;
btnRelay5On.Enabled = false;
btnRelay1Off.Enabled = false;
btnRelay2Off.Enabled = false;
btnRelay3Off.Enabled = false;
btnRelay4Off.Enabled = false;
btnRelay5Off.Enabled = false;
}
}
private void btnRelay1On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH1");
}
private void btnRelay1Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL1");
}
private void btnRelay2On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH2");
}
private void btnRelay2Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL2");
}
private void btnRelay3On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH3");
}
private void btnRelay3Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL3");
}
private void btnRelay4On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH4");
}
private void btnRelay4Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL4");
}
private void btnRelay5On_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OH5");
}
private void btnRelay5Off_Click(object sender, EventArgs e)
{
SerialPortClass._instance.serialSendString(1, "OL5");
}
private void btnQuery_Click(object sender, EventArgs e)
{
//this.BeginInvoke(new SetTextCallback(SetText), new object[] { "hjdfdsfj" });
SerialPortClass._instance.serialSendString(1, "?");
Console.WriteLine("?");
}
}
}
Also there's references to BackgroundWorker in the Designer file, so I've included it here too:
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
//
// backgroundWorker1
//
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//private System.ComponentModel.BackgroundWorker backgroundWorker1;
What you are missing is the initialization of the BackGroundWorker. You should do this in the constructor:
public Form2()
{
InitializeComponent();
this.backgroundWorker1 = new BackGroundWorker();
// here you have also to implement the necessary events
// this event will define what the worker is actually supposed to do
this.backgroundWorker1 .DoWork += backgroundWorker1r_DoWork;
// this event will define what the worker will do when finished
this.backgroundWorker1 .RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
EDIT:
As I read your post a little more clearly. If you want to:
I try to run the BackGroundWorker from SerialPortClass
You have to make sure that you have an instance of the Form2 in your SerialPortClass. Note that if you just use the new keyword it might not be the same instance as is already shown on your monitor.
EDIT 2:
Ok there seems to be a pattern emerging. Please correct me if I am wrong
As I understand you open Form1 where you have a field SerialPortClass sp_class. In Form1 you press a button and this button calls the method:
sp_class.SetText();
Now you have a problem because apparently when you come to the line:
form2.backgroundWorker1.RunWorkerAsync();
form2 is null because it has never been instantiated! Please do the following: Create an instance and open the form from the SerialPortClass like this:
form2 = new Form2();
form2.backgroundWorker1.RunWorkerAsync();
form2.Show();
Now, the event registration of the backgroundWorker1 still belongs into the Form2 class , because this is where you actually let it run! The instance is still in Form2 please don't get mixed up in this triangle of Form1, Form2 and SerialPortClass
EDIT 3:
Since your SerialPort triggers the backgroundWorker1 you should already in the constructor of the SeralPortClass make an instance of the Form2 like this:
public SerialPortClass()
{
form2 = new Form2();
serialPort1.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
But what you really should do to unwind the knot that you created between your 3 classes which seem to depend on each other, is to pass the instance of the Form2 to the constructor of the SerialPortClass like this:
public SerialPortClass(Form2 f2)
{
form2 = f2
serialPort1.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
and I guess in Form1 you make an instance of Form2 which you call and the SerialPortClass which you use to call SetText. And exactly there you need to pass the instance into the contructor call:
SerialPortClass my_sp_class = new SerialPortClass(form2);
This will make sure that you get the display of text on the desired form
The issue is here.
form2.backgroundWorker1.RunWorkerAsync();
You have created a backgroundwoker but not registered any methods/delegates to it.
Assign it by adding this in constructor of form2,
this.backgroundWorker1.DoWork += backgroundWorker1_DoWork; //not found in snippet
this.backgroundWorker1.RunWorkerCompleted +=backgroundWorker1_RunWorkerCompleted;
Also, as mentioned by Mong-Zhu, you need to initialize Backgroundworker like this in form2.
this.backgroundWorker1 = new BackGroundWorker();
Also, I personally think it is not a good idea to call backgroundworker of different form.
After reading the comments and such, I can see that you're not referencing the same form2, and you're also not initializing a new one, meaning the said null reference is invalid.
When you do the code
Form2 form2;
This is simply preparing a variable to be assigned, and since you haven't done this you can't access it's objects. If you wish to reference your form2, initialise it globally where you're able to access it, and from there you can use it's objects.
If you need any more guidance just reply :)
It seems that your use of BackgroundWorker is incomplete. You can use it with the Designer, which is easier, or in the code-behind.
To use with the designer, drag and drop a backgroundWorker object onto the component tray. Then click on the newly created BackgroundWorker1 object. Go over to the properties tray and select the lightning bolt for events. There are 3 events, the two that you will need are the DoWork and RunWorkerCompleted. Double-click on both to generate the methods for you.
It appears in your code snippet that you are missing the DoWork part, as well as not actually instantiating your BackGroundWorker object. The way it flows is this: The BackgroundWorker is declared and initialized. When you need to make an asynchronous call you raise the .RunWorkerAsync event. That RunWorkerAsync event will make its way to your DoWork event handler. This is where you place the code for the work you wish to do asynchronously. Once this DoWork event expires, the RunWorkercompleted event is called. Once this RunWorkerCompleted event expires, so does the new thread.
If you try to change designer components while in a different thread than the thread it was created on (i.e. setting button text from inside the DoWork event handler for the BackgroundWorker), you will have to ensure to use the .InvokeRequired, as it appears you have already started.
If you are calling this backgroundworker from outside of form2 make sure that you are giving the handle to form2 to that class. It is unclear from the snippet whether you instantiated a new one or not. An easy way is to make a private field for Form2 in your Serialization class, as well as a method, say AttachForm(Form f) and then call that method from Form2, passing this as a parameter.
I'm new with .Net Threads. I understand that we can't work with WinForm GUI out of the main thread.
I want one of my method that update the WinForm GUI to run in the main thread right after a 2nd thread ends.
Here is a part of my code:
public class FormGApp : Form
{
private Thread m_LoginThread;
private void buttonLogin_Click(object sender, EventArgs e)
{
m_LoginThread = new Thread(new ThreadStart(this.login));
m_LoginThread.Start();
}
private void login()
{
LoginResult result = loginToServer();
this.User = result.LoggedInUser;
}
private void successfullyLogin()
{
// Update the WinForn GUI here...
// This method must run in the main thread!!!
}
}
How can I run the method successfullyLogin() when m_LoginThread ends?
You have a couple of options:
As #ScottChamberlain said in the comment, use a BackgroundWorker and use its Completed event to update the GUI
Use TPL Library in the following way:
Task.Run(() =>
{
//do work
}).ContinueWith(() =>
{
//do continuation
}, TaskScheduler.FromCurrentSynchronizationContext);
Use Application.Current.BeginInvoke or Application.Current.Invoke from your background thread
If you are using .Net 4.5, you can use async/await
async private void buttonLogin_Click(object sender, EventArgs e)
{
await Task.Run(() => login());
successfullyLogin();
}
Thanks you all to inspire my to use BackgroundWorker, it indeed solved this issue.
Here is my solution:
public partial class FormGApp : Form
{
private BackgroundWorker m_LoginBackgroundWorker;
// ctor:
public FormGApp()
{
// init thread:
this.m_LoginBackgroundWorker = new BackgroundWorker();
this.m_LoginBackgroundWorker.DoWork += this.LoginBackgroundWorker_DoWork;
this.m_LoginBackgroundWorker.RunWorkerCompleted += this.LoginBackgroundWorker_RunWorkerCompleted;
}
private void buttonLogin_Click(object sender, EventArgs e)
{
// start thread:
this.m_LoginBackgroundWorker.RunWorkerAsync();
}
private void LoginBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
this.login();
}
private void LoginBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.successfullyLogin();
}
private void login()
{
// code that take long time that executed in a separate thread...
}
private void successfullyLogin()
{
// Gui WinForm update code here...
}
I can't use eventArgs from a method trigged for a second Thread:
public class MovilinkCommunication
{
//Method Declarations
public delegate void MovilinkWatchParametersEventMethod(ParameterAddress sender, MovilinkEventArgs e);
private MovilinkWatchParametersEventMethod onWatchParameterMethod;
//class constructor
//here, the user inputs the method (in main thread) that desires to call in
//parameter changed moment
public MovilinkCommunication(MovilinkWatchParametersEventMethod userOnWatchParameterMethod)
{
//assign user method (in main thread) to wach variables
onWatchParameterMethod = userOnWatchParameterMethod;
//start communication thread (second thread)
Thread movilinkThread = new Thread(new ThreadStart(movilinkIOManagerThread));
movilinkThread.Start();
}
.
.
.
//create delegates with "sender" parameter and "e" conditions of call
delegate void CallOnWatchParameterMethod(ParameterAddress sender, MovilinkEventArgs e);
private void callOnWatchParameterMethod(ParameterAddress sender, MovilinkEventArgs e)
{
//calling user method in main thread with event args obtained in
//communication thread (second thread)
onWatchParameterMethod(sender, e);
}
.
.
.
//communication thread
private void movilinkIOManagerThread()
{
ParameterAddress sender;
MovilinkEventArgs e;
.
.
.
while (movilinkAccessor.OperationStatusOk)
{
.
.
.
CallOnWatchParameterMethod thdCallOnWatchParameterMethod =
new CallOnWatchParameterMethod(callOnWatchParameterMethod);
Dispatcher.CurrentDispatcher.Invoke(thdCallOnWatchParameterMethod, new object[] { sender, e });
.
.
.
}
}
}
Works fine, but when I try use "sender" and "e" event args in user method (in main thread), the message bellow appears:
"The calling thread cannot access this object because a different thread owns it."
Can someone give me a hint about this problem? Thanks,
Jeferson
Follow Tudor, thanks again. This code is in window.xaml.cs code. The code in first post is in MovilinkComunication.cs.
MovilinkCommunication comunicadorMovilink;
private void wndPrincipal_Loaded(object sender, RoutedEventArgs e)
{
//creating communication object, setting the desired event
//to be trigged in secundary thread
comunicadorMovilink =
new MovilinkCommunication(getChangeParameters_Movilink);
}
.
.
.
//desired method to made actions in window, if detected
//change of parameters in external hardware
private void getChangeParameters_Movilink(ParameterAddress sender, MovilinkEventArgs e)
{
//error occurs here. Any code with GUI return error.
label24.Content = e.ActualValue.ToString();
}
The label update needs to be done via Dispatcher.BeginInvoke:
private void getChangeParameters_Movilink(ParameterAddress sender, MovilinkEventArgs e)
{
label24.Dispatcher.BeginInvoke(
(Action)(() =>
{
label24.Content = e.ActualValue.ToString();
}));
}
if your application is winforms, you can do this
public void d()
{
if (this.InvokeRequired)
{
BeginInvoke( new MethodInvoker( delegate() {
foo(a, b);
} ) );
}
else
{
foo(a, b);
}
}
private void foo(int a, int b)
{
}
in this example, d and foo are located in the form's class
Thanks a lot, this works fine
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate()
{
printausfueren();
}));
}
else
{
printausfueren();
}
hey i am new to c# plz help.
i am writing a program that sorts data in a file and it is a time consuming process so i thought that i should run it in a separate thread and since it has alot of step so i made a new class for it. the problem is that i want to show the progress in the main GUI and i know for that i have to use Invoke function but the problem is that the form control variables are not accessible it this class. what should i do ??????
sample code:
public class Sorter
{
private string _path;
public Sorter(string path)
{
_path = path;
}
public void StartSort()
{
try
{
processFiles(_path, "h4x0r"); // Just kidding
}
catch (Exception e)
{
MessageBox.Show("Error: " + e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void processFiles(string Dir, string[] key)
{
/* sorting program */
}
and it is used as
public partial class Form1 : Form
{
Sorter sort;
public Form1()
{
InitializeComponent();
}
private void browseBtn_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
textBox1.Text = folderBrowserDialog1.SelectedPath;
}
private void startBtn_Click(object sender, EventArgs e)
{
if (startBtn.Text == "Start Sorting")
{
Thread worker = new Thread(new ThreadStart(delegate() {
sort = new Sorter(textBox1.Text);
sort.StartSort(); }));
worker.start();
}
else
MessageBox.Show("Cancel");//TODO: add cancelling code here
}
}
plz help..
Add an Event to your class that is doing the multi-threaded work, that triggers when the progress changes. Have your form subscribe to this event and update the progress bar.
Note ProgressEventArgs is a little class that inherits EventArgs and has an Integer for the progress.
// delegate to update progress
public delegate void ProgressChangedEventHandler(Object sender, ProgressEventArgs e);
// Event added to your worker class.
public event ProgressChangedEventHandler ProgressUpdateEvent
// Method to raise the event
public void UpdateProgress(object sender, ProgressEventArgs e)
{
ProgressChangedEventHandler handler;
lock (progressUpdateEventLock)
{
handler = progressUpdateEvent;
}
if (handler != null)
handler(sender, e);
}
I would recommend you read up on the BackgroundWorker class. It is exactly for the problem you are trying to solve and makes things a lot easier than doing manual threading yourself.
Brief Example
public Form1()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void btnStart_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i < 101; ++i)
{
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
//Sort Logic is in here.
Thread.Sleep(250);
backgroundWorker.ReportProgress(i);
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy && backgroundWorker.WorkerSupportsCancellation)
backgroundWorker.CancelAsync();
}
You could do something like this:
public delegate void StatusReporter(double progressPercentage);
public class MainClass
{
public void MainMethod()
{
Worker worker = new Worker(ReportProgress);
ThreadStart start = worker.DoWork;
Thread workThread = new Thread(start);
workThread.Start();
}
private void ReportProgress(double progressPercentage)
{
//Report here!!!
}
}
public class Worker
{
private readonly StatusReporter _reportProgress;
public Worker(StatusReporter reportProgress)
{
_reportProgress = reportProgress;
}
public void DoWork()
{
for (int i = 0; i < 100; i++ )
{
// WORK, WORK, WORK
_reportProgress(i);
}
}
}
There are a few option available to solve this sort of issue. In any case, you will have to fiddle with Invoke to get the UI to update.
You could...
...add an event that fires on your new class which your UI can listen to, and Invoke as applicable - you'd still need to pass the data to your worker class (by constructor, properties, method call, etc)
...keep the method as a method on your form, and pas that to start your new thread from (after all, a new thread doesn't have to be starting in a different class)
...change the access modifiers on your controls to be (say) internal such that any class within the same assembly can Invoke changes to the controls, or read from them.
...make your worker class a child of the form it needs to access - it can then see the privates of its parent, as long as it is passed a reference to the instance.
using System;
using System.Windows.Forms;
using agsXMPP;
using System.Text;
namespace iTalk2
{
public partial class Main : Form
{
agsXMPP.XmppClientConnection objXmpp;
public Main()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Console.WriteLine("Logging in. Please wait...");
Console.ReadLine();
objXmpp = new agsXMPP.XmppClientConnection();
agsXMPP.Jid jid = null;
jid = new agsXMPP.Jid("username" + "#gmail.com");
objXmpp.Password = "password";
objXmpp.Username = jid.User;
objXmpp.Server = jid.Server;
objXmpp.AutoResolveConnectServer = true;
try
{
objXmpp.OnMessage += messageReceived;
objXmpp.OnAuthError += loginFailed;
objXmpp.OnLogin += loggedIn;
objXmpp.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
private void messageReceived(object sender, agsXMPP.protocol.client.Message msg)
{
string[] chatMessage = null;
chatMessage = msg.From.ToString().Split('/');
agsXMPP.Jid jid = null;
jid = new agsXMPP.Jid(chatMessage[0]);
agsXMPP.protocol.client.Message autoReply = null;
autoReply = new agsXMPP.protocol.client.Message(jid, agsXMPP.protocol.client.MessageType.chat, "This is a test");
objXmpp.Send(autoReply);
}
private void loginFailed(object o, agsXMPP.Xml.Dom.Element el)
{
Console.WriteLine("Login failed. Please check your details.");
}
private void loggedIn(object o)
{
Console.WriteLine("Logged in and Active.");
lblStatus.Text = "Online";
}
private void txtUsername_TextChanged(object sender, EventArgs e)
{
}
private void label1_Click(object sender, EventArgs e)
{
}
private void label2_Click(object sender, EventArgs e)
{
}
private void txtPassword_TextChanged(object sender, EventArgs e)
{
}
private void btnlogin_Click(object sender, EventArgs e)
{
}
}
}
This code is not working. the function 'loggedIn(object o)' is not working. it says the lblStatus (which is a label) is on another thread. the error window says "Cross-thread operation not valid: Control 'lblStatus' accessed from a thread other than the thread it was created on." thanks in advance.
You need to invoke a call on the UI thread. If you add code as follows at the top of the loggedIn method it should work:-
if(InvokeRequired)
{
Invoke(new Action<object>(loggedIn), o);
return;
}
WinForms is designed such that controls must only be manipulated on the UI-thread, the thread that runs the message-loop that manages the control.
Try this instead:
private void loggedIn(object o)
{
Console.WriteLine("Logged in and Active.");
Action act = () => lblStatus.Text = "Online";
Invoke(act);
}
If your application is such that this method can be called on the UI thread or a separate worker thread, you'd be better off testing forInvokeRequired(simply: am I on the control's UI thread?) and dealing with the result appropriately. For example,
private void loggedIn(object o)
{
if(InvokeRequired)
Invoke(new Action<object>(loggedIn), o);
else
{
Console.WriteLine("Logged in and Active.");
lblStatus.Text = "Online";
}
}
Note that Invokewill block until the UI-update is completed. If you want something more fire-and-forget, use BeginInvokeinstead.
When you start an application it is running from a single thread. This is the main thread, sometimes called the UI thread (since the UI will usually be rendered at startup and as a consequence it will be on that main thread.
Now, when you listen to events, your methods/delegates will get called from new threads. This is a consequence of the event based design. Normally this is not a problem unless you are trying to share data between two threads. This is exactly what happens with your UI elements. In this case your UI elements were created by your first thread but other threads are trying to update its value.
Given your design, you should check for IsInvokeRequired on the control and if so, use Invoke to set the new value. This will marshal your call from the new thread into the main thread that your UI is running on and will allow you to safely change the control.