How to put a cancel button when connecting

Viewed 121

I make a connection in C# with a login and a password and I would like put a button to cancel the connection to the database if it gets too much long.

I would like to know how to do it and put it in a thread if possible.

Here is the code:

private void btncon_Click(object sender, EventArgs e)
{
    string strLogin = tblogin.Text.Trim();
    string pass  = tbpwd.Text;
    bool success = false;

    if (String.IsNullOrWhiteSpace(strLogin) || String.IsNullOrWhiteSpace(pass))
    {
        MessageBox.Show("Veuillez remplir tous les champs SVP ");
    }
    else if(String.IsNullOrEmpty(strLogin) || String.IsNullOrEmpty(pass)){
        MessageBox.Show("Veuillez remplir tous les champs SVP ");
    }
    else
    {
        model.Connexion cm = new model.Connexion();
        pass = Snippets.SHA1Util.SHA1HashStringForUTF8String(pass).ToString();
        string[] user = cm.login(strLogin, pass);    

        if(user[0] != null)
        {
            Int32.TryParse(user[0], out iduser);
            Int32.TryParse(user[1], out idGrp);
            Int32.TryParse(user[2], out idbtq);
            nom = user[3];

            if (idGrp != 3)
                success = true;
            else
            {
                MessageBox.Show("Accès Non autorisé , Veuillez contacter l'administrateur");
                success = false;
            }
        }
        else
        {
            MessageBox.Show("Email ou mot de passe incorrect.");
            success = false;
        }
    }

    if (success)
    {                
        main Principale = new main();
        Principale.Show();
        Hide();`
    }
}
1 Answers

Problem is that when user clicks the Cancel button, there is no way to cancel the cm.login() method gracefully while it's executing. You could use Thread.Abort() to terminate login forcefully, but it's unsafe and strongly discouraged, because making it right would require execute code in another AppDomain and would make the code very complicated.

Fortunatelly, you can still implement Cancel button, if the following conditions are true:

  • It is safe to call cm.login() on another thread
  • Letting cm.login() finish in the background (on another thread) after user clicks on the Cancel button does not have undesired effects.

This code also assumes that it is an Winform app (but solution for WPF app would be very similar). It also assumes that main form has a button btnCancel that is hidden (Visible=false) and it's Click event handler is set to btnCancel_Click method.

TaskCompletionSource<Object> CancelLoginTcs = new TaskCompletionSource<Object>();

// Make button click handler method async
private async void btncon_Click(object sender, EventArgs e)
{
    try
    {
        // Make Cancel button visible, so that user can click on it
        btnCancel.Visible = true;

        // Prepare everything needed to start login
        //var strLogin = ...;
        //var pass = ...;
        //model.Connexion cm = new model.Connexion();
        // ...

        // Start login on another thread
        var loginTask = Task<string[]>.Run(() => cm.login(strLogin, pass));

        // Create task that is used to wake-up main thread, when user clicks
        // on the Cancel button before login finishes.
        CancelLoginTcs = new TaskCompletionSource<Object>();

        // Wait for login task or cancel task to complete, whichever finishes first
        await Task.WhenAny(loginTask, CancelLoginTcs.Task);

        if (CancelLoginTcs.Task.IsCanceled)
        {
            // User clicked on the Cancel button.
            // Login method will be running in the background, until it
            // finishes. This assumes that it is safe to do so.
            // Here you should do neccessary clean-up, inform user, etc.
            // ...
        }
        else
        {
            // Login finished and user did NOT click on the Cancel button.

            try
            {
                // Simply read result of login. If an Exception occured during login,
                // it will be rethrow now, so you should handle it appropriatelly
                var user = loginTask.Result;

                // Here program continues in a usual way
                // ...
            }
            catch(Exception E)
            {
                // Handle login exception
                // ...
            }
        }
    }
    finally
    {
        // Hide Cancel button again
        btnCancel.Visible = false;
        CancelLoginTcs = null;
    }
}

private void btnCancel_Click(object sender, EventArgs e)
{
    // Set cancel task to cancelled state. 
    // This will wake-up main thread and let it continue
    CancelLoginTcs.SetCanceled();
}
Related