简体   繁体   中英

C# Need to wait for thread to finish without blocking UI thread

I display a ProgressBar on the UI thread and then kickoff a logon process on another thread. This works flawlessly, I get the ProgressBar "circle" which lets the user know something is going on. The problem is I need to return any logon errors, like invalid username/password to the user via the UI. I changed my ProgressBar class to contain an ErrorMessage property, instantiate it and pass that as a parameter to the logon process. On any errors I set the ErrorMessage property. If it's not null, I display the error. The problem is the logon thread doesn't finish before I get to the if that checks for null. This is all done on a button click event. My code:

MyButton_Submit.Click += async (sender, e) =>
{
        ManualResetEvent resetEvent = new ManualResetEvent(false);
        ProgressBarHandler myprogressbar = new ProgressBarHandler(this);
        myprogressbar.show();

        var thread = new System.Threading.Thread(new ThreadStart(async delegate
        {
            await SignOn(myprogressbar);
            resetEvent.Set();
        }));
        thread.Start();
        // based on @Giorgi Chkhikvadze comment below I changed: resetEvent.WaitOne(); to:

        await Task.Run(() => resetEvent.WaitOne());
        // works perfectly 


        while (thread.ThreadState == ThreadState.Running)
        {
            await Task.Delay(100);
        }
        myprogressbar.hide();

        if (myprogressbar.ErrorMesage != null)
        {
            Context context = Application.Context;
            string text = myprogressbar.ErrorMesage ;
            ToastLength duration = ToastLength.Short;
            var toast = Toast.MakeText(context, text, duration);
            toast.Show();
        }
    };
}

The line resetEvent.WaitOne(); is obviously blocking the UI because when I comment it out, the progressbar is displayed, when I execute it, it does not. How can I fix this?

* EDIT - Added SignOn Code *

    private async Task SignOn(ProgressBarHandler MyProgress)
    {
        Boolean error = false;

        // Hide the keyboard ...
        InputMethodManager imm = (InputMethodManager)GetSystemService(Context.InputMethodService);
        imm.HideSoftInputFromWindow(aTextboxPassword.WindowToken, 0);

        // Check permissions
        var mypermission = ApplicationContext.CheckCallingOrSelfPermission(Android.Manifest.Permission.Internet);

        if (ApplicationContext.CheckCallingOrSelfPermission(Android.Manifest.Permission.Internet) != Android.Content.PM.Permission.Granted)
        {

            int MY_REQUEST_CODE = 0;
            //int x = 0;
            RequestPermissions(new String[] { Android.Manifest.Permission.Internet },
                    MY_REQUEST_CODE);
            //x = 1;
        }

        mypermission = ApplicationContext.CheckCallingOrSelfPermission(Android.Manifest.Permission.Internet);

        if (ApplicationContext.CheckCallingOrSelfPermission(Android.Manifest.Permission.AccessNetworkState) != Android.Content.PM.Permission.Granted)
        {

            int MY_REQUEST_CODE = 0;
            RequestPermissions(new String[] { Android.Manifest.Permission.AccessNetworkState },
                    MY_REQUEST_CODE);
        }

        MyUser.Username = aTextboxUsername.Text.Trim();
        MyUser.Password = aTextboxPassword.Text.Trim();
        try
        {

            ConnectivityManager connectivityManager = (ConnectivityManager)GetSystemService(ConnectivityService);

            NetworkInfo activeConnection = connectivityManager.ActiveNetworkInfo;
            bool isOnline = (activeConnection != null) && activeConnection.IsConnected;

            if (isOnline)
            {
                ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
                OMLDataInterfaceWeb.OMLDataInterface myService = new OMLDataInterfaceWeb.DataInterface();

                try
                {
                    result = myService.Logon(MyUser.Username, MyUser.Password);

                }
                catch (Exception ex)
                {
                    MyProgress.ErrorMesage = "Logon attempt failed due to error: " + ex.Message;
                }
            }
            else
            {
                MyProgress.ErrorMesage = "There is no internet connection or cell phone connection.  Connect to a network or connect to a cellular network.";
            };
        }
        catch (Exception ex)
        {
            MyProgress.ErrorMesage = "Connectivity Manager failed to create a connection due to error: " + ex.Message;
        };

        if (result == "CONNECTED")
        {
            PopulateMyUser();
            StoreUsernamePassword();


            var usertype = MyUser.Usertype.ToUpper();
            if (usertype == "ADMIN")
            {
                Intent intent = new Intent(this, typeof(Drawer));
                Bundle bundlee = new Bundle();
                bundlee.PutParcelable("MyUser", MyUser); // Persist user class to next activity
                intent.PutExtra("TheBundle", bundlee);
                StartActivity(intent);
            }

        }
        else
        {
            try
            {
                error = true;
                errormsg = "Logon Error: Invalid Username or Password.";
            }
            catch (Exception ex)
            {
                MyProgress.ErrorMesage = "An error occured while attempting to set the error message, the error is: " + ex.Message;
            }
        };
        try
        {
            if (error)
            {
                MyProgress.ErrorMesage = "An error occured during the logon process (2), The error is: " + errormsg;
            }
        }
        catch (Exception ex)
        {
            MyProgress.ErrorMesage = "An error occured during the logon process (2), The error is: " + ex.Message;
        }
    }

* NOTE: * After posting this code I see I need to do some more work with permissions, the permissions dialog is not likely to appear to the user on this thread so I'll need to move it out of this process.

You don't need a separate thread; await will work just fine on its own. You also don't need ManualResetEvent as a "signal" that the work is done; using await on a Task works fine for this.

Equivalent, simplified code:

MyButton_Submit.Click += async (sender, e) =>
{
  ProgressBarHandler myprogressbar = new ProgressBarHandler(this);
  myprogressbar.show();

  await SignOn(myprogressbar);
  myprogressbar.hide();

  if (myprogressbar.ErrorMesage != null)
  {
    ...
  }
};

You should never the Thread type in a Xamarin app. There is always a simpler, better way. The Thread type is a "strong yellow" flag in general, but a red flag in Xamarin.

await Task.Run(() => resetEvent.WaitOne()); should do the trick. ManualResetEvent is not awaitable and can't use await keyword directly on it, so you need to wrap it in task

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM