Using invoke without a control - c#

I have a Component (System.ComponentModel.Component)
This component receives an event from another Thread. Normally I would use this pattern to execute this code in the GUI thread
private void handle_Event(object sender, EventArgs e)
{
var control = this.Button1;
if (control.InvokeRequired)
control.Invoke(() => DoSomething());
else
DoSomeThing();
}
However, in this special case I don't have a control. How do I do this?

If you pass the current SynchronizationContext to the background thread, you can ask it to Post (begininvoke) or Send (invoke) the code required on the foreground thread.
Here's a simple LINQPad program to demonstrate:
void Main()
{
using (var fm = new Form())
{
var btn = new Button();
fm.Controls.Add(btn);
btn.Click += HandleClick;
Thread.CurrentThread.ManagedThreadId.Dump("Main thread");
fm.ShowDialog();
}
}
public static void HandleClick(object sender, EventArgs e)
{
var synchronizationContext = SynchronizationContext.Current;
var thread = new Thread(new ThreadStart(
() => BackgroundMethod(synchronizationContext)));
thread.Start();
}
public static void BackgroundMethod(SynchronizationContext context)
{
context.Post(state =>
{
Thread.CurrentThread.ManagedThreadId.Dump("Invoked thread");
}, null);
}

Use Dispatcher to run your code in GUI thread...hope am getting your problem right.
private void handle_Event(object sender, EventArgs e)
{
this.Button1.Dispatcher.Invoke(
DispatcherPriority.Normal, (Action)() => {
DoSomething();
});
}
Please check for the syntax errors :|

Related

Access to controls from another thread

I dont can get access to checkBox control, how I can do it?
I have code what must checking CheckBox properties of checked into another thread
Thread thread1 = new Thread(fbd);
static void fbd()
{
if (chb1.Checked == true)
{
}
}
private void button1_Click(object sender, EventArgs e)
{
thread1.Start();
}
If using this method then compiler showing error.
If add links thread starting, but process doesnt work
Form1 form = new Form();
if (form.chb1.Checked == true)
{
\\действие
}
System.Threading.Thread.Sleep(form.name);
How I can get access to controls and variables from another thread. Thank you.
Can you try this:
private void fbd()
{
if (chb1.Checked == true)
{
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(new ThreadStart(fbd));
thread1.Start();
}

How to run a method in the main thread after the sub-thread ends?

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...
}

NullReferenceException happens when sending message back to message dialog from BackGroundWorker

It's a C# winform .Net framework 2.0 project: The time-consuming network transferring jobs run in a BackupgroundWorker. In this backgroundwork thread, SynchronizationContext method is used to send the current job title to a message dialog in the main UI thread. However, in my code, thread racing problem happens and "NullReferenceException" is given sometime (not always, but randomly). Here is my code:
private void DoBtn_Click(object sender, EventArgs e)
{
m_msgForm = new MsgForm(); //m_msgForm is a member variable of the class, and MsgForm is a form class with a "public static SynchronizationContext synContext"
m_msgForm.UpdateMsg("starting ..."); //UpdateMsg is public method to show progress information
BackgroundWorker myBackgroundWorker = new BackgroundWorker();
myBackgroundWorker.DoWork +=new DoWorkEventHandler(myBackgroundWorker_DoWork);
myBackgroundWorker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(myBackgroundWorker_RunWorkerCompleted);
myBackgroundWorker.RunWorkerAsync(theBackgroundArgument);
m_msgForm.ShowDialog(); //show as a modal dialog
}
And in the Background worker thread:
private void myBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//... some code
string msgText ="doing job: " +job.Title;
RestoreMsgForm.synContext.Send(m_msgForm.UpdateMsg, msgText); //send message to m_msgForm in the UI thread
//... some code
}
The problem is : sometime the "RestoreMsgForm.synContext.Send() in the backgroundworker thread will run before m_msgForm.ShowDialog() in the UI thead.
And in this case, NullReferenceException will be throw.
How to solve this problem? thanks.
the definition of RestoreMsgForm is
public partial class RestoreMsgForm : Form
{
public static SynchronizationContext synContext;
public RestoreMsgForm()
{
InitializeComponent();
}
private void RestoreMsgForm_Load(object sender, EventArgs e)
{
synContext = SynchronizationContext.Current;
}
public void UpdateMsg(object msg)
{
msgLabel.Text = (string)msg;
}
}
See if it works like this instead:
private void DoBtn_Click(object sender, EventArgs e)
{
m_msgForm = new MsgForm(); //m_msgForm is a member variable of the class, and MsgForm is a form class with a "public static SynchronizationContext synContext"
m_msgForm.UpdateMsg("starting ..."); //UpdateMsg is public method to show progress information
BackgroundWorker myBackgroundWorker = new BackgroundWorker();
myBackgroundWorker.DoWork += new DoWorkEventHandler(myBackgroundWorker_DoWork);
myBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myBackgroundWorker_RunWorkerCompleted);
myBackgroundWorker.WorkerReportsProgress = true;
myBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(myBackgroundWorker_ProgressChanged);
myBackgroundWorker.RunWorkerAsync(theBackgroundArgument);
}
private void myBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
worker.ReportProgress(-1);
//... some code
string msgText = "doing job: " + job.Title;
worker.ReportProgress(0, msgText);
//... some code
worker.ReportProgress(0, "...other text...");
//... some code
worker.ReportProgress(0, "...etc...");
}
void myBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == -1)
{
m_msgForm.ShowDialog(); //show as a modal dialog
}
else
{
m_msgForm.UpdateMsg(e.UserState.ToString);
}
}
*Note that we have to set .WorkerReportsProgress = true for the BackgroundWorker() so we can use ReportProgress() and receive the ProgressChanged() event.

Communicate Between two threads

I have sth like that. It's giving me error. I cut out all unneeded parts of code. It is giving me this error
The calling thread cannot access this object because a different thread owns it.
public partial class MainWindow : Window
{
BackgroundWorker worker;
Grafik MainGrafik;
double ProgressBar
{
set { this.progressBarMain.Value = value; }
}
public MainWindow()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
MainGrafik = new Grafik();
MainGrafik.ProgressUpdate +=
new Grafik.ProgressUpdateDelegate(MainGrafik_ProgressUpdate);
worker.RunWorkerAsync();
}
void MainGrafik_ProgressUpdate(double progress)
{
ProgressBar = progress;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
while(true)
{
MainGrafik.Refresh();
Thread.Sleep(2000);
}
}
}
class Grafik
{
public delegate void ProgressUpdateDelegate(double progress,
DateTime currTime);
public event ProgressUpdateDelegate ProgressUpdate;
public void Refresh()
{
ProgressUpdate(5); // Just for testing
}
}
You can't update UI objects from another thread. They have to be updated in the UI thread. Try adding this code to the MainGrafik_ProgressUpdate(double progress)
void MainGragfik_ProgressUpdate(double progress)
{
if (InvokeRequired)
{
BeginInvoke((MethodIvoker)(() =>
{
MainGragfik_ProgressUpdate(progress);
}));
return;
}
ProgressBar = progress;
}
The thread firing the ProgressUpdate event is your BackgroundWorker. The ProgressUpdate event handlers are likely running on that thread, and not the UI thread.
in short call this on the form in the context of your other thread's execution:
void MainGrafik_ProgressUpdate(object sender, EventArgs e) {
Action<T> yourAction =>() yourAction;
if(yourForm.InvokeRequired)
yourForm.Invoke(yourAction);
else yourAction;
}
Or with MethodInvoker (blank delegate)
void MainGrafik_ProgressUpdate(object sender, EventArgs e) {
MethodInvoker invoker = delegate(object sender, EventArgs e) {
this.ProgressBar = whatever progress;
};
}

cross thread call

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.

Categories