1

I've got a problem with my Xamarin App. The App uses a custom API to my website. I have not much experience in async/await methods.

The following code shows the App.xaml.cs:

public partial class App : Application
{
    public static bool IsUserLoggedIn { get; set; }
    public static UserModel currentLoggedInUser { get; set; }

    public static List<ActionTypeModel> listTypes;
    public static List<ActionSubTypeModel> listSubtypes;
    public static List<UserModel> listFriends;
    public static List<List<ActionModel>> listActiveActions;
    public static List<ActionModel> listLastAction;

    public App()
    {
        this.InitializeComponent();

        APIHelper.InitializeClient();

        StartApp().Wait();
    }

    private async Task StartApp()
    {
        //// DEBUG
        //MyAccountStorage.Logout();

        string username = await MyAccountStorage.GetUsername().ConfigureAwait(false);
        string password = await MyAccountStorage.GetPassword().ConfigureAwait(false);
        string user_id = await MyAccountStorage.GetId().ConfigureAwait(false);
        if (username != null && password != null)
        {

            currentLoggedInUser = new UserModel();
            if (user_id != null)
                currentLoggedInUser.user_id = Convert.ToInt32(user_id);
            currentLoggedInUser.username = username;
            currentLoggedInUser.password = password;

            bool isValid = false;
            isValid = await AreCredentialsCorrect(0, currentLoggedInUser.username, currentLoggedInUser.password).ConfigureAwait(false);
            if (isValid)
            {
                IsUserLoggedIn = true;

                await FillLists().ConfigureAwait(false);

                MainPage = new NavigationPage(await MyPage.BuildMyPage().ConfigureAwait(false));
            }
            else
            {
                IsUserLoggedIn = false;

                MainPage = new NavigationPage(await LoginPage.BuildLoginPage().ConfigureAwait(false));
            }

        }
        else
        {
            IsUserLoggedIn = false;

            MainPage = new NavigationPage(await LoginPage.BuildLoginPage().ConfigureAwait(false));
        }
    }

    private async Task FillLists()
    {
        listFriends = await DataControl.GetFriends(App.currentLoggedInUser.user_id, App.currentLoggedInUser.username, App.currentLoggedInUser.password).ConfigureAwait(false);
        if (listFriends == null)
            listFriends = new List<UserModel>();

        listTypes = await DataControl.GetTypes(App.currentLoggedInUser.username, App.currentLoggedInUser.password).ConfigureAwait(false);
        if (listTypes == null)
            listTypes = new List<ActionTypeModel>();

        listActiveActions = new List<List<ActionModel>>();

        for (int i = 0; i < listTypes.Count; i++)
            listActiveActions.Add(await DataControl.GetActiveActions(listTypes[i].action_type_id, currentLoggedInUser.user_id, currentLoggedInUser.username, currentLoggedInUser.password).ConfigureAwait(false));

        listSubtypes = await DataControl.GetSubtypes(App.currentLoggedInUser.username, App.currentLoggedInUser.password).ConfigureAwait(false);
        if (listSubtypes == null)
            listSubtypes = new List<ActionSubTypeModel>();

        listLastAction = await DataControl.GetLastAction(App.currentLoggedInUser.user_id, App.currentLoggedInUser.username, App.currentLoggedInUser.password).ConfigureAwait(false);
        if (listLastAction == null)
            listLastAction = new List<ActionModel>();
    }

    public static async Task<bool> AreCredentialsCorrect(int type, string user, string pass, string nick = "", string email = "")
    {
        List<UserModel> listUsers;

        if (type == 1)
            listUsers = await DataControl.CheckCredentials(1, user, pass, nick, email).ConfigureAwait(false);
        else
            listUsers = await DataControl.CheckCredentials(0, user, pass).ConfigureAwait(false);

        if (listUsers != null)
            if (listUsers.Any())
            {
                currentLoggedInUser = listUsers.First();
                currentLoggedInUser.password = pass;
                return true;
            }

        return false;
    }
}

I have the API in DataControl.cs:

public static async Task<List<UserModel>> CheckCredentials(int type, string username, string pass, string email = "", string nickname = "")
{
    string password = APIHelper.GetHashSha256(pass);

    string url = string.Empty;

    if (type == 0)
        url = APIHelper.ApiClient.BaseAddress + "/account/login.php?username=" + username + "&password=" + password;
    if (type == 1)
    {
        string nick = string.Empty;
        if (string.IsNullOrEmpty(nickname) == false)
            nick = "&nickname=" + nickname;

        url = APIHelper.ApiClient.BaseAddress + "/account/signup.php?username=" + username + "&password=" + password + "&email=" + email + nick;
    }
    if (string.IsNullOrEmpty(url))
        return null;

    using (HttpResponseMessage response = await APIHelper.ApiClient.GetAsync(url).ConfigureAwait(false))
    {
        if (response.IsSuccessStatusCode)
        {
            List<UserModel> listUsers = JsonConvert.DeserializeObject<List<UserModel>>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));

            return listUsers;
        }
        else
            return null;
    }
}

That's one of the different async methods. When I leave out the ConfigureAwait(false), I run into a deadlock. When I add it to the code I run into an error.

Could you please help me.

AlKaramba
  • 11
  • 1
  • 2
    `When I leave out the "ConfigureAwait(false)" I run into a deadlock` - that strongly suggests you are [blocking on async](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) somewhere. Such as at `StartApp().Wait();` in your [constructor](https://stackoverflow.com/q/8145479/11683). Don't do this. – GSerg Feb 22 '20 at 19:49
  • The App constructor was the only position I used the Wait() method. Every other method is async and uses await to be called. – AlKaramba Feb 22 '20 at 20:01
  • Yes, one is enough. – GSerg Feb 22 '20 at 20:02
  • If I remove the Wait() the application is doing nothing and I see in the debug output an endless row of Thread pool startet and Thread pool finished... – AlKaramba Feb 22 '20 at 20:18
  • You can't use an async function from a constructor. You don't just need to remove `Wait` (which results in a [fire-and-forget](https://stackoverflow.com/q/46053175/11683)), you need to restructure your program so that you don't call an async method from a constructor. – GSerg Feb 22 '20 at 20:20
  • Thank you. I guess I have taken the first step in the right direction. However, now I get the error the following error `Android.Util.AndroidRuntimeException: Only the original thread that created a view hierarchy can touch its views.` I already read that it is because of the `ConfigureAwait(false)` at methods that are connected to UI objects. Could you please clarify if I am right? – AlKaramba Feb 22 '20 at 21:38
  • Yes, it is. `ConfigureAwait(false)` allows the continuation to continue on a random thread (that is, it allows it to not bother with resuming on the captured synchronization context, which for all practical purposes translates to resuming on a random thread). This is not an option in code that needs to keep affinity to a certain thread, such as code that manages UI. – GSerg Feb 23 '20 at 06:40

1 Answers1

-3

You can use Task.Run( //call your async method here );

So in your case:

 Task.Run(Startup);
javachipper
  • 509
  • 4
  • 14