0

I have been through quite a number of questions and read through the examples and answers, yet I still can't seem to wrap my head around how to achieve this properly.

I have a button which toggles an external Login / Logout function (imported from a DLL and wrapped in a class) - this login can take up to about 10 seconds to complete. While this function is running, the main Form hangs.

What I would like to do is return a success / fail from this login function, then update the UI with the result. In the meantime while the login function is processing, the UI thread shouldn't be blocked.

My button code is here (please forgive my bad code, I am still very much a beginner):

private async void btnConnect_Click(object sender, EventArgs e)
{
    btnConnect.Enabled = false;
    if (pw.Initialised)
    {
        bool isLoggedIn = pw.LoggedIn;
        if (isLoggedIn)
        {
            isLoggedIn = await Task.Run(() => pw.LogOut());
            if (!isLoggedIn)
            {
                lblConnection.Text = "Disconnected";
                lblDataSource.Text = "N/A";
                lblUserName.Text = "N/A";
                lblAdmin.Text = "N/A";
                btnConnect.Text = "Connect";
                btnChooseSet.Enabled = false;
            }
        }
        else
        {
            string dataSourceName = cbDataSource.SelectedItem.ToString();
            IntPtr formHandle = Handle;
            bool autoLogin = cbAutoLogin.Checked;
            isLoggedIn = await Task.Run(() => pw.LogIn(dataSourceName, autoLogin,
                formHandle));
            if (isLoggedIn)
            {
                lblConnection.Text = "Connected";
                lblDataSource.Text = pw.CurrentDataSource;
                lblUserName.Text = pw.CurrentUser.UserName;
                lblAdmin.Text = pw.CurrentUser.IsAdmin.ToString();
                btnConnect.Text = "Disconnect";
                btnChooseSet.Enabled = true;
            }
        }
    }
    btnConnect.Enabled = true;
}

The way I understand this should work (if I am reading all these posts correctly, especially this one), is that the button code should execute all the way up to the await statement, fire off the task in the background, then effectively return and process the message pump until the awaited task pings, says "I'm ready", and queues up the rest of the method for execution.

In reality, this function blocks the UI thread until the task returns and execution continues.

I'm at my wits end with this to be honest, I've tried creating a separate async task and awaiting that, doing all the async functions inside of the class (at the moment the class method is not async), and a whole host of other things that didn't work that I can't remember.

The class method is here (the external DLL function is the call to aaApi_LoginDlgExt:

public bool LogIn(string dataSourceName, bool autoLogIn, IntPtr formHandle)
{
    if (dataSourceName == null)
    {
        OnLogEvent("Select a datasource from the drop-down menu!");
        return false;
    }

    int maxLen = 256;
    StringBuilder sbDSName = new StringBuilder(dataSourceName, maxLen);

    uint uiLoginFlags = 0x00000000;
    if (autoLogIn)
    {
        uiLoginFlags |= (uint)LoginDialogStyleFlags.AALOGIN_SILENT;
    }            
    if (aaApi_LoginDlgExt(formHandle, null, uiLoginFlags, sbDSName, maxLen,
        null, null, null) == (int)DialogBoxCommandId.IDOK)
    {
        isLoggedIn = true;
        currentDataSource = sbDSName.ToString();
        currentDataSourceHandle = aaApi_GetActiveDatasource();
        currentUser = new UserData();
        OnLogEvent("Logged in");
        return true;
    }
    else
    {
        OnLogEvent("Failed to Log In!");
        return false;
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Britishly
  • 9
  • 1
  • 3
    Your `LogIn()` method takes a `formHandle` and also seems to forward it by calling `aaApi_LoginDlgExt()`. Due to the fact, that this method seems to open up a UI dialog it has to operate on the UI thread and thous blocking your UI. So the problematic part is not your code, but whatever happens in `aaApi_LoginDlgExt()`. So either this library supports some login method without UI or some async method. Otherwise no way. – Oliver Jan 11 '22 at 11:53
  • @Oliver Thank you - I hadn't considered that. The library method _can_ show a dialog, however in the instance that I pass in `autoLogin` as true, it will attempt to login in the background using stored credentials. If that fails it shows a dialog. I hadn't considered that the external library might be doing something that's blocking the calling UI thread. – Britishly Jan 11 '22 at 12:00
  • 1
    The current code looks ok, so I agree with @Oliver that the problem is inside `aaApi_LoginDlgExt()`. Can you run the code under debugger with mixed debugging enabled and then examine stack when the main form hangs? The stack may show which exactly blocks the UI – Serg Jan 11 '22 at 12:03
  • 1
    Just a guess from reading code. But you should check (with the debugger) which method really blocks. Maybe it is `aaApi.GetActiveDatasource()` or something else. So check which method really blocks. If it doesn't use any UI, put it into an `await Task.Run()` otherwise you're in trouble. – Oliver Jan 11 '22 at 12:04
  • Okay - well I'm still quite new to this and there are no symbols for the library I'm using (it's closed source) so I'm going to go away and learn how to use the disassembler and figure out how to find out which code is blocking. Ultimately it's not the end of the world - having to wait 10 seconds for a function to return isn't fantastic but it doesn't make it completely unusable. – Britishly Jan 11 '22 at 12:11
  • If you are using a recent version of visual studio, you might be able to [decompile the code directly from the debugger](https://devblogs.microsoft.com/visualstudio/decompilation-of-c-code-made-easy-with-visual-studio/). In my experience it does not always work, but it is worth a try. You might also want to check out [dnSpy](https://github.com/dnSpy/dnSpy) for a stand alone debugger/decompiler. – JonasH Jan 11 '22 at 12:43
  • 1
    What happens if you pass `IntPtr.Zero` instead of `Form.Handle` to the `aaApi_LoginDlgExt` method? – Theodor Zoulias Jan 11 '22 at 12:56
  • 1
    @TheodorZoulias Passing IntPtr.Zero looks like it works! I guess then that as said, the external method was blocking the UI thread from running when the form's handle was passed as a parent to the dialog. It's worth noting that both automatic and manual login (manual with the dialog box) work just fine, and the UI thread looks like it's running as expected. Thanks guys! – Britishly Jan 11 '22 at 16:11
  • 1
    I would be a bit careful passing a empty window handle. If you are unlucky the library might create another UI thread, and that may have unintended consequences. So I would at least read up on the documentation if there is any. – JonasH Jan 11 '22 at 16:15
  • 1
    @JonasH using an API that apparently has insufficient documentation (or no documentation at all), is dangerous either way IMHO. – Theodor Zoulias Jan 12 '22 at 18:44
  • There is documentation but it's quite spartan and not exactly a joy to go through. Unfortunately an alternative simply doesn't exist, so it is what is. I need to read up on what information they provide regarding threading however from my experience it's probably going to be fruitless. Thanks guys, I appreciate all the insight! – Britishly Jan 13 '22 at 19:26

0 Answers0