Correctly create a task for log-in function - c#

I was working on my windows form program, and i saw that the login function (linked to a simple button) freeze my application. I searched on internet and i found how to create a task, but i'm not sure about how it works ...
That's my login function, how can i correctly translate it into a task?
string sURL = url + "/login";
string result = null;
await Task.Run(() =>
{
try
{
result = Web_api.MakeRequest("POST", sURL); //return null if there is some error
}
catch(Exception ex)
{
Debug.WriteLine("[frmLogin] --> result: " + result);
}
});
if(result != null)
{
try
{
Login_response accepted = JsonConvert.DeserializeObject<Login_response>(result);
Debug.WriteLine("[frm_Login] --> accepted: " + accepted);
if (accepted.login)
{
//throw new Exception();
Debug.WriteLine("[frm_login]: result " + result);
frmMain frm = new frmMain(); //calling the new form
frm.Show(); //new form is show-up
this.Hide(); //log-in form hide
frm.FormClosed += Frm_FormClosed; //close the form
}
}
//if server is down, or the id or password is wrong
catch (Exception ex)
{
lblLoginError.Visible = true; //pop-up the error label
pbLogin.Visible = false; //hide the progress-bar
this.Style = MetroFramework.MetroColorStyle.Red; //changing the color of the form
Debug.WriteLine("Exception: " + ex);
}
}
else
{
lblLoginError.Visible = true; //pop-up the error label
pbLogin.Visible = false; //hide the progress-bar
this.Style = MetroFramework.MetroColorStyle.Red; //changing the color of the form
}
EDIT: i provided a real (and working) soluction and i followed all the suggestion in the comments ... do you think this could be acceptable?

Execute any potentially long-running code on a background thread using a Task:
private async void btnLogin_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtUser.Text.Trim()) || string.IsNullOrEmpty(txtPassword.Text.Trim()))
{
MessageBox.Show("You must insert a valid user/password format", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
//Progress bar start
pbLogin.Visible = true; // BUT THIS PROGRESS BAR I STACK DUE TO STATIC DEFINITON OF LOGIN
//Getting ID + Password
User.username = txtUser.Text;
User.password = txtPassword.Text;
string sURL = Web_api.url + "/login";
try
{
Login_response accepted = await Task.Run(() =>
{
//the following code gets executed on a background thread...
string result = Web_api.MakeRequest("POST", sURL);
Login_response accepted = JsonConvert.DeserializeObject<Login_response>(result);
Debug.WriteLine("[frm_Login] --> accepted: " + accepted);
return accepted;
});
//...and here you are back on the UI thread once the task has completed
if (accepted.login)
{
//throw new Exception();
Debug.WriteLine("[frm_login]: result " + result);
frmMain frm = new frmMain(); //calling the new form
frm.Show(); //new form is show-up
this.Hide(); //log-in form hide
frm.FormClosed += Frm_FormClosed; //close the form
}
}
//if server is down, or the id or password is wrong
catch (Exception ex)
{
lblLoginError.Visible = true; //pop-up the error label
pbLogin.Visible = false; //hide the progress-bar
this.Style = MetroFramework.MetroColorStyle.Red; //changing the color of the form
Debug.WriteLine("Exception: " + ex);
}
}
Event handlers always return void. They are an exception to the rule that says that an async method always should return a Task or a Task<T>.

You can create an async void method. It is actually the correct way to implement async callbacks for events such as button click.
First, let's make an asynchronous login method :
public async Task LoginAsync()
{
try
{
var stream = await _http.GetStreamAsync($"{baseUrl}/login");
var response = await JsonSerializer.DeserializeAsync<LoginResponse>(stream);
if (!response.login)
{
throw new BusinessException<LoginError>(LoginError.LoginDenied);
}
}
catch (HttpRequestException ex)
{
throw new BusinessException<LoginError>(LoginError.LoginFailed, ex);
}
}
Now we implement an asynchronous button callback:
private async void btnLogin_Click(object sender, EventArgs e)
{
try
{
await authentication.LoginAsync().ConfigureAwait(false);
// handle login success here
}
catch (BusinessException<LoginError> ex) when (ex.Error == LoginError.LoginDenied)
{
// handle login denied here
}
catch (BusinessException<LoginError> ex) when (ex.Error == LoginError.LoginFailed)
{
// handle connection failed here
}
}
If you want the LoginAsync() method to do some UI operations (for instance showing a form), you will need to use ConfigureAwait(true). Otherwise, there is a risk that part of the method is executed on a different thread and UI operations will fail.

Related

Async function freezes UI thread

I have an async function which still freezes / lags the UI thread for me when I execute it. This is my function calling it.
private void TcpListenerLogic(object sender, string e)
{
Application.Current.Dispatcher.BeginInvoke((Action)async delegate {
try
{
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
{
// Get properties for new anchor
string testInformation = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + results.test_id);
}
}
catch (Exception exception)
{
// Writing some Trace.WriteLine()'s
}
});
}
And this is the async function that freezes my UI Thread
public static async Task<string> getJsonFromURL(string url)
{
try
{
string returnString = null;
using (System.Net.WebClient client = new System.Net.WebClient())
{
returnString = await client.DownloadStringTaskAsync(url);
}
return returnString;
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return null;
}
}
I already tried to make everything in TcpListenerLogic run in a new Thread:
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
}).Start();
Which resulted in the whole UI completely freezing. And I tried to make TcpListenerLogic async and await the dispatcher, which also made everything freeze permanently. I also tried to make TcpListenerLogic async and leave the dispatcher. The dispatcher is only there because I normally have some UI code in there, which I left out for my tests.
I have ventured far through the internet, but no BackgroundWorker, ThreadPool or other methods helped me in my endeavour.
If anyone has help for this particular problem, or a resource that would improve my understanding of async functions in C#, I would much appreciate it.
Edit
As requested a deeper insight in how this event handler is called.
I have System.Net.Websocket, which is connected to the Backend API I am working with and triggers an event, everytime he receives new Data. To guarantee the socket listens as longs as it is open, there is a while loop which checks for the client state:
public event EventHandler<string> TcpReceived;
public async void StartListener(string ip, int port, string path)
{
try
{
using (client = new ClientWebSocket())
{
try
{ // Connect to backend
Uri serverUri = new Uri("ws://" + ip + ":" + port.ToString() + path );
await client.ConnectAsync(serverUri, CancellationToken.None);
}
catch (Exception ex)
{
BackendSettings.IsConnected = false;
Debug.WriteLine("Error connecting TCP Socket: " + ex.ToString());
}
state = client.State;
// Grab packages send in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
{
try
{
// **Just formatting the received data until here and writing it into the "message" variable**//
TcpReceived(this, message);
// Close connection on command
if (result.MessageType == WebSocketMessageType.Close)
{
Debug.WriteLine("Closing TCP Socket.");
shouldstayclosed = true;
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
state = client.State;
}
catch
{
BackendSettings.IsConnected = false;
state = client.State;
}
}
state = client.State;
}
}
catch (Exception ex)
{
// Some error messages and settings handling
}
}
The Event has a handler attached:
TcpReceived += TcpListener_TcpReceived;
And this is the Handler, which calls the previously seen "TcpListenereLogic".
private void TcpListener_TcpReceived(object sender, string e)
{
TcpListenerLogic(sender, e);
//App.Current.Dispatcher.BeginInvoke(new Action(() => {
// TcpListenerLogic(sender, e);
//}));
//new Thread(() =>
//{
// Thread.CurrentThread.IsBackground = true;
// TcpListenerLogic(sender, e);
//}).Start();
}
I previously had the "TcpListenereLogic" as the handler, but I wanted to try different methods to call it. I also left in the commented out part, to show how the call of "TcpListenereLogic" looked already. All my attempts were with all mentioned setups and sadly lead to nothing.
Thank you very much #TheodorZoulias for helping me to find the solution to my problem.
It turns out it wasn't the async function itself, but rather how often it gets called. It got called roughly ~120 times every second.
My solution starts by calling the Listener method over a new Thread:
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
MainWindow.tcpListener.StartListener(ip, portNumber, "/api/");
}).Start();
To limit the amount of calls that happen every second I added a dispatcher timer, that resets a bool after it has been used for a call, by my Event.
readonly System.Windows.Threading.DispatcherTimer packageIntervallTimer =
new System.Windows.Threading.DispatcherTimer();
bool readyForNewPackage = true;
private void ReadyForPackage(object sender, EventArgs e)
{
readyForNewPackage = true;
}
public async void StartListener(string ip, int port, string path)
{
packageIntervallTimer.Interval = TimeSpan.FromMilliseconds(50);
packageIntervallTimer.Tick += (s, e) => { Task.Run(() => ReadyForPackage(s, e)); };
packageIntervallTimer.Start();
Then I wrapped everything inside the while loop into an if condition based on the bool, the most important part was to have my "event EventHandler TcpReceived" in there:
// Grab packages sent in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
{
if (readyForNewPackage == true)
{
readyForNewPackage = false;
try
{
....
TcpReceived(this, message);
....
}
catch
{
...
}
}
}
I added my TcpListenerLogic to the Eventhandler:
TcpReceived += TcpListenerLogic;
And my TcpListenerLogic now looked like this (names have been changed):
private async void TcpListenerLogic(object sender, string e)
{
try
{
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
{
string testID = "";
if (results.test_id is JValue jValueTestId)
{
testID = jValueTestId.Value.ToString();
}
else if (results.test_id is string)
{
testID = results.test_id;
}
// Get properties for new object
string information = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + testID );
if (information != null)
{
await App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
// Create object out of the json string
TestStatus testStatus = new TestStatus();
testStatus.Deserialize(information);
if (CommunicationCommands.isNameAlreadyInCollection(testStatus.name) == false)
{
// Add new object to the list
CommunicationCommands.allFoundTests.Add(testStatus);
}
}));
{
}
catch (Exception exception)
{
....
}
}
Adding a new Thread to execute any step results in problems, so keep in mind that all this uses the thread created at the beginning for "StartListener"

How to open form inside method, submit button and then come back to original method and continue?

Our application requires that a witness must authenticate before a logged in user can perform enrollment operations (Enroll and Delete).
This is not an issue for Enrolling as I can add a check (IsWitnessApproved) to an enrollment_OnStartEnroll method I.E. before the Capture method is called and fired.
However, this is not possible for Deletion as I don't have access to a point where the enrollment_OnDelete method hasn't fired.
I haven't been able to get a response to this issue from Digital Persona so I'm now looking at work-arounds.
I'm exploring if its possible to open up a new form (WitnessApproval) inside the enrollment_OnDelete method, approve the witness in the form (btnConfirmWitness_Click) and then come back into the method and continue on with the deletion?
enrollment_OnDelete method:
private void enrollment_OnDelete(DPCtlUruNet.EnrollmentControl enrollmentControl, Constants.ResultCode result, int fingerPosition)
{
if (!witnessApproved)
{
WitnessApproval witnessApproval = new WitnessApproval();
witnessApproval.Show();
}
else
{
int fingerMask = GetFingerMask(fingerPosition);
if (enrollmentControl.Reader != null)
{
try
{
// Delete from database
new EnrollmentDAL().DeleteEnrolledFingerprint(Settings.Default.Username, fingerMask, txt_WitnessName.Text);
MessageBox.Show("Fingerprint deleted.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information);
pbFingerprint.Image = null;
pbFingerprint.Visible = false;
btnCancel.Visible = false;
witnessApproved = false;
txt_WitnessName.Text = String.Empty;
txt_WitnessPassword.Text = String.Empty;
}
catch (Exception ex)
{
MessageBox.Show("There was a problem deleting the fingerprint.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
new Util().LogError(ex);
}
}
else
{
MessageBox.Show("No Reader Connected.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
_sender.Fmds.Remove(fingerPosition);
}
}
Selected WitnessApproval methods:
private void btnConfirmWitness_Click(object sender, EventArgs e)
{
lbl_Validation.Visible = false;
if (txt_WitnessName.Text == String.Empty)
{
SetMessage("Please enter a Witness.");
return;
}
if (txt_WitnessPassword.Text == String.Empty)
{
SetMessage("Please enter a Password.");
return;
}
if (txt_WitnessName.Text == Settings.Default.Username)
{
SetMessage("User and witness cannot be the same.");
return;
}
bool IsValidate = Membership.ValidateUser(txt_WitnessName.Text, txt_WitnessPassword.Text);
Settings.Default.WitnessName = txt_WitnessName.Text;
Settings.Default.WitnessPassword = txt_WitnessPassword.Text;
if (IsValidate)
{
this.Close();
// Allow enrollment operations
}
else
{
SetMessage("Witness credentials invalid.");
}
}
private void btnCancelWitness_Click(object sender, EventArgs e)
{
this.Close();
// DO NOT Allow enrollment operations
witnessCancelled = true;
}
private void SetMessage(string message)
{
lbl_Validation.Visible = true;
lbl_Validation.Text = message;
}
How to open form inside method, submit button and then come back to original method and continue?
There is ShowDialog method for this purposes.
Here is usage example from MSDN:
public void ShowMyDialogBox()
{
Form2 testDialog = new Form2();
// Show testDialog as a modal dialog and determine if DialogResult = OK.
if (testDialog.ShowDialog(this) == DialogResult.OK)
{
// Read the contents of testDialog's TextBox.
this.txtResult.Text = testDialog.TextBox1.Text;
}
else
{
this.txtResult.Text = "Cancelled";
}
testDialog.Dispose();
}
In your case, Form2 is WitnessApproval.
In WitnessApproval Form button handlers you will also need to set DialogResult to true when the witness is approved or to false when user cancelled operation.

Check my SQL connection every second without freezing

First of all, I apologize for my English.
I have a method to verify my connection to SQL (TestConneccion), and I use a timer to verify it every second.
The problem is when I lose the connection, my application hangs while it tries to connect. I'm using task to avoid this, but I am new to C#.
I would really appreciate the help
public string testConeccion()
{
var archivo = "";
try
{
odb = DatabaseFactory.CreateDatabase("TESTCONECTION");
ocn = odb.CreateConnection();
if (ocn.State == ConnectionState.Closed)
{
ocn.Open();
}
ocn.Close();
archivo = "true";
}
catch (InvalidOperationException ex)
{
archivo = ex.Message;
}
catch (Exception ex)
{
archivo = ex.Message;
}
finally
{
ocn.Close();
}
return archivo;
}
private void timerMesas_Tick(object sender, EventArgs e)
{
Task<string> T1 = Task.Run<string>(() => oClasePublica.testConeccion());
if (T1.Result == "true")
{
btnEstado.Image = new System.Drawing.Bitmap(TOUCHREST.Properties.Resources.Status_32x32);
}
else
{
btnEstado.Image = new System.Drawing.Bitmap(TOUCHREST.Properties.Resources.Warning_32x32);
}
}
Make your timer tick handler async and await your task. This will run the method asynchronously and prevent the rest of your application from hanging while it runs.
private async void timerMesas_Tick(object sender, EventArgs e)
{
string T1 = await Task.Run<string>(() => oClasePublica.testConeccion());
if (T1 == "true")
{
btnEstado.Image = new System.Drawing.Bitmap(TOUCHREST.Properties.Resources.Status_32x32);
}
else
{
btnEstado.Image = new System.Drawing.Bitmap(TOUCHREST.Properties.Resources.Warning_32x32);
}
}
The ocn.Close() in the try code is not needed because the finally will do it. Also catch (InvalidOperationException ex) is not needed because catch (Exception) covers it.
You cannot do Task.Run and then immediately test the Result because the task has not yet finished! Normally you use Task.Wait(); however this will then block your main thread.
I suggest using a BackgroundWorker that continuously checks the connection and then reports it progress. This avoids the issue of changing the image on a non-UI thread.

Callback problems with WPF

I've been running into a Callback problems with async programming in WPF .Net 4.5.
There should be a way of doing this code in a more understandable way (I have suppressed a big part of it to make it easier to see what is going on).
I don't think there is a way to simply remove code because I need to call Dispatcher in order to manipulate WPF controls and calls like in TbSequence.Focus() and Utils.ShowMessageBox.
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.SaveItem();
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Dispatcher.BeginInvoke(new Action(() => Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage)));
}
}
Controller.Busy = false;
});
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
public void Init(UcEdit container, Employee entity = null)
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.Init(entity);
}
catch (BdlEntryNotFoundException ex)
{
HandleNotFoundException(ex);
}
Container.Dispatcher.BeginInvoke(new Action(() =>
{
Container.DataContext = Controller;
// Instructions order for focusing TbSequence after load should be different in case we have an existent item
// because we have to focus the control AFTER it is filled with data, in order to set the caret position correctly.
if (Controller.IsNewItem)
{
this.DataContext = Controller;
TbSequence.Focus();
Controller.Busy = false;
}
else
{
TbSequence.TextChanged += TbSequence_TextChanged;
this.DataContext = Controller;
SetButtons();
}
}));
});
}
private void HandleUniqueViolation(BdlDbException ex)
{
string errorMessage = "";
bool isSequence = false; // if true, it's name.
if (ex.Fields[1] == "Sequence")
{
errorMessage = "There is already an Employee with the sequence \"" + Controller.Item.Sequence + "\".";
isSequence = true;
}
else
{
errorMessage = "There is already an Employee named \"" + Controller.Item.FirstName +
" " + Controller.Item.LastName + "\".";
}
errorMessage += "\r\nLoad it from Database?\r\n(All the changes on this window will be lost.)";
Dispatcher.BeginInvoke(new Action(() =>
{
MessageBoxResult res = Utils.ShowMessageBox(t_MessageBox.Question, errorMessage, MessageBoxButton.YesNo);
switch (res)
{
case MessageBoxResult.Yes:
if (isSequence)
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeBySequence(Controller.Item.Sequence);
Init(Container, Controller.OriginalItem);
});
}
else
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeByName(Controller.Item.FirstName, Controller.Item.LastName);
Init(Container, Controller.OriginalItem);
});
}
break;
case MessageBoxResult.No:
break;
}
}));
}
As you can see, there is a major Callback problem here that behaves like this:
Save_Executed (UI) -> HandleUniqueViolation (Task) -> ShowMessageBox (UI) -> Controller.GetEmployeeBySequence (Task) -> Controller.Init ...
And so it goes.
Is there a way to make this code more easy to read?
Thank you.
You're starting off your code by putting (more or less) the entirety of your method body in a call to Task.Run and then explicitly marshalling to the UI thread all over the place within that callback.
Just narrow the scope of your Task.Run call so that your UI code is just outside the call to Run rather than inside both it and a call to Invoke:
private async void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
try
{
await Task.Run(() => Controller.SaveItem());
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage);
}
}
Controller.Busy = false;
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
Here you're running the actual long running business operation that you have in a thread pool thread, but doing all of your error handling in the UI thread.
You can do the same thing throughout your application. Rather than putting everything into a background thread and then explicitly marshaling, just only ever execute operations in a background thread that should be entirely executed in a background thread.

Suppress navigation while awaiting result on Windows Phone 8.1

I would like to prevent changing the current page by pressing Back button or any navigation events while awaiting not finished. Because then exception happens, its should shown in the same page, on the other case it would be difficult to understand what action generate this exception
private async void AppBarButton_Click(object sender, RoutedEventArgs e)
{
MessageDialog dialog = null;
try
{
progressRing.IsActive = true;
this.IsEnabled = false;
commandBar1.IsEnabled = false;
await GlobalVars.API.Call(CC.ChangeDate, date);
var notify = new MessageDialog("Done");
await notify.ShowAsync();
}
catch (Exception ex)
{
dialog = new MessageDialog(ex.Message);
}
finally
{
progressRing.IsActive = false;
this.IsEnabled = true;
commandBar1.IsEnabled = true;
}
if (dialog != null) await dialog.ShowAsync(); // must be shown in the same page
}
You can intercept the back button using:
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
}
Obviously you can do whatever you want in there.

Categories

Resources