When GetDataAsync executed, textBox1_Validating event is raised before textbox1_Leave event finished. how can I do to prevent this situation?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private bool IsValid = true;
private async void textBox1_Leave(object sender, EventArgs e)
{
MessageBox.Show("Working");
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
IsValid = await client.CheckUser(textBox1.Text);
}
private void textBox1_Validating(object sender, CancelEventArgs e)
{
if(IsValid)
MessageBox.Show("Welcome!");
else
e.Cancel = true;
}
}
From Control.Validating:
events occur in the following order:
Enter
GotFocus
Leave
Validating
Validated
LostFocus
When you await inside Control.Leave, you let the UI message pump continue execution, hence it proccess the next event. If you want to wait until Leave finishes, run your method synchronously.
The Validating process of a control is a synchronous process, you can't have it wait until you return from your asynchronous method before continuing. The point of async / await is to allow the UI continue whilst you wait on the result of your asynchronous method therefore once you await inside the Leave event the control assumes it's complete and continues on with the rest of the event chain.
The Validating event should be used to perform synchronous validation, if you need server validation then you just have to accept that the text entered is valid and on then Validated event you could then send off your request
private bool IsValid = false;
private void textBox1_Validated(object sender, EventArgs e)
{
this.ValidateUser(textBox1.Text);
}
private async void ValidateUser(string username)
{
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
IsValid = await client.CheckUser(textBox1.Text);
if (IsValid) {
MessageBox.Show("Welcome!");
} else {
MessageBox.Show("Invalid Username, try again!");
textBox1.Focus();
}
}
Related
I'm working on Xamarin Forms project in Visual Studio 2017 . I need to implement async method inside thread .
Event which starting the thread
public void btnAction_Click(object sender, System.EventArgs e)
{
var load = new System.Threading.Thread((t) =>
{
ShowWarning();
});
load.Start(btnText);
}
async method should implement inside the thread
private async void ShowWarning()
{
bool response = await DisplayAlert("Warning", "Please Enter
The Key","Yes","No");
}
If DisplayAlert is truely asynchronous, why do you start a thread at all? You can declare your event handler async and simply awiat ShowWarning:
public async void btnAction_Click(object sender, System.EventArgs e)
{
await ShowWarning();
}
You might want to disable btnAction before the await and re-enable it after the await again, so you avoid another click event while awaiting.
And note that it's bad practice to declare ShowWarning as async void. It should be
private async Task ShowWarning()
{ ... }
(In case of the button event handler it's ok to return async void because otherwise you couldn't assign it to an event).
If you really need to run ShowWarning on a different thread, you can use Task.Run():
public async void btnAction_Click(object sender, System.EventArgs e)
{
await Task.Run(ShowWarning);
}
I have a class like that "ClientSocket.cs"
class ClientSocket {
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
ev(this, e);
}
}
And another class "myForm.cs"
public partial class myForm : Form {
private ClientSocket client;
private void button1_Click(object sender, EventArgs e) {
client = new ClientSocket();
client.ConnectEvent += myForm_OnConnectEvent;
client.connect();
}
// Handler for ConnectEvent
private void myForm_OnConnectEvent(object sender, EventArgs e) {
//this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
writeLog("Connected");
}
// Function that write a log string to a TextBox
public writeLog(string log) {
guiTextBox.AppendText(log);
}
}
Here the question.
I try to call writeLog with BeginInvoke or calling it directly. Sometimes I get an InvalidOperationException when writing to guiTextBox.
I don't understand why I receive that message. The event is fired by ClientSocket object, but the event handler is relative to the main UI-thread (myForm).
Can I avoid to use BeginInvoke/Invoke for each EventHandler of my class?
EDIT: I understand what's the difference, now I'm try to understand the best practice for calling the event.
Should I put the BeginInvoke/Invoke method when RAISING the event in the BASE class (ClientSocket in that case)
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
this.BeginInvoke((MethodInvoker)delegate { ev(this, e);});
}
or should I put that WHEN I'm using that object and add a listeners to that handler
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e) {
this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
}
Cheers
the this.BeginInvoke inside ClientSocket does not exist. To be able to do the BeginInvoke it must be called on a object that has that method (your form in your case).
If you wanted the invoking to happen inside your ClientSocket class you would need to pass in a Control that has the BeginInvoke function.
However if I where writing this I would not do this approach. It adds a unnecessary requirement to ClientSocket that you must have a Control passed in (this is called Tightly Coupling and you should try to avoid it in your programming). Personally I would let the event pass along in whatever thread it wants to be raised in and let the consumer worry about doing any special invoking (if they even need to at all).
Here is how I would write myForm_OnConnectEvent, this pattern checks to see if we need to invoke and if we do it calls the function again with the same arguments but this time on the UI thread.
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
if(this.InvokeRequired)
{
this.BeginInvoke(new ConnectHandler(myForm_OnConnectEvent), new object[] {sender, e});
return;
}
writeLog("Connected");
}
As a side note, I don't know what writeLog is doing (it should have a capital W by the way) but if it is not interacting with the UI you don't need to do any invoking at all. If it interacts with a TextBox or something else on the UI, that is where I would do my invoking.
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
writeLog("Connected");
}
private void writeLog(string logMessage)
{
if(logTextBox.InvokeRequired)
{
logTextBox.BeginInvoke(new Action<string>(writeLog), logMessage);
return;
}
var logLine = String.Format("{0:g}: {1}{2}", DateTime.Now, logMessage, Enviorment.NewLine);
logTextBox.AppendText(logLine);
}
The event handler is declared in myForm, but the thread, which executes handler, is defined by the logic of ClientSocket class. If this will be background thread, event handler will be raised from background thread, so, you'll need BeginInvoke to avoid cross-thread access to controls.
In other words: belonging of any method of any type isn't related to the thread, which will ever execute this method. These things (types and threads) are parallel universes.
By the way, you can replace this:
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
with this:
public event EventHandler ConnectEvent;
There's no need to make yet another delegate type.
I'm running a C# forms application which starts a thread to acquire some data. This thread has some events inside it i.e: the events fire in the thread and are supposed to be captured by the same thread. However, the thread's events don't seem to be firing. Any clues?
private void btnPlay_Click(object sender, EventArgs e)
{
Thread thread = new Thread(kinect.onlineRun);
thread.IsBackground = true;
thread.Start();
}
inside the thread:
void PointCreated(object sender, IdEventArgs e) // a certain event that should fire and it doesn't
{
Console.WriteLine("Event Fired!");
}
public void onlinerun()
{
Console.WriteLine("run started"); // this is printed on console
while (true)
{
do_some_work();
//this work could result in the PointCreated event firing
}
}
Give something like this a try:
Assuming your calling class is called Controller and your delegate is called ControlEventHandler...
private void PointCreated(object sender, IdEventArgs e)
{
// Ensure the event was received in the calling thread
if (this.InvokeRequired)
{
if (e != null)
{
// We aren't in the correct thread so pass on the event
this.BeginInvoke(new Controller.ControllerEventHandler(this.PointCreated), new object[] { sender, e });
}
}
else
{
lock (this)
{
Console.WriteLine("Event Fired!");
// TODO: Do some stuff here
}
}
}
In the main thread I have a Timer. In the Tick event I run a BackgroundWorker. I do some things there and after that BackgroundWorker calls RunWorkerCompleted event.
In the main thread I have function Stop. This function disables the Timer. But I want wait for BackgroundWorker when he is working.
For example:
public void Next()
{
// Start the asynchronous operation
if (!this._backgroundWorker.IsBusy)
this._backgroundWorker.RunWorkerAsync();
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DoSomething();
}
private void _backgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
DoSomethingElse();
}
public void Stop()
{
this._timer.Enabled = false;
}
So my question is how wait for RunWorkerCompleted event of BackgroundWorker? I need to wait until DoSomethingElse(); is finished.
Thanks
Handle the BackgroundWorker.RunWorkerCompleted event which occures when the background operation has completed, has been canceled, or has raised an exception.
// This event handler deals with the results of the
// background operation.
private void backgroundWorker1_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
}
else
{
// Finally, handle the case where the operation
// succeeded.
}
}
If you only require two threads, allow the thread that called this._backgroundWorker.RunWorkerAsync();
to die after it calls this method and call anything you want to occur after DoSomethingElse(); within the same block as below
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DoSomethingElse();
DoSomethingAfterSomethingElse();
}
Otherwise you are halting a thread to start another and then returning, which defeats the purpose of multiple threads?
I think BackgroundWorker.IsBusy property is the only member that can help you in this case. Hope below logic will do what you need.
//Add a class member
private bool stopped;
public void Stop()
{
if (!this._backgroundWorker.IsBusy)
{
this._timer.Enabled = false;
stopped = false;
}
else
{
stopped = true;
}
}
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DoSomethingElse();
if (stopped)
{
this._timer.Enabled = false;
stopped = false;
}
}
Here is a way to stop/freeze the main thread until your background worker finishes:
public void Stop()
{
if (!_backgroundWorker.IsBusy)
{
_timer.Enabled = false;
// Stop/Freeze the main thread until the background worker finishes
while (_backgroundWorker.IsBusy)
{
Thread.Sleep(100);
}
}
}
Now if your application uses a form, I would just disable the whole form and show message letting the user know the the application is waiting for the process to finish. You can also have flag to disable the form from closing.
private bool _canClose;
public void Stop()
{
if (!_backgroundWorker.IsBusy)
{
_timer.Enabled = false;
// Don't let the user do anything in the form until the background worker finishes
this.IsEnabled = false;
_label.Text = "Waiting for the process to finish";
_canClose = false;
}
}
private void _backgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
DoSomethingElse();
// Allow the user to close the form
this.IsEnabled = true;
_canClose = true;
}
private void MainWindow_Closing(object sender, CancelEventArgs e)
{
e.Cancel = !_canClose;
}
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.