Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Best practise - waiting and synchronising threads

MartHughMartHugh USMember ✭✭✭
edited May 2015 in Xamarin.Forms

I wish to throw a dialog box up from ServerCreateCertificateCallBack. The dialog will warn a user about a certificate which appears invalid. Depending on what they press (OK / Cancel), the connect attempt will go ahead or the App will be closed.

I have a routine which deals with showing the dialog, using UIAlertAction, and this work OK (ShowOKCancel() )

I cannot call ShowOKCancel from ServerCreateCertificateCallBack, because the former needs to be called on the main thread, so, I call it from InvokeOnMainThread().

I also have a boolean flag bflag which I need to set based on the outcome of the users response which will determine whether or not the connection goes ahead

    ServerPointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => {

    // test parameters and show a dialog if there is a problem
    InvokeOnMainThread(ShowOKCancel);

    return bflag
    };

Problems

1) How do I synchronise the two threads so that the Callback waits for the ShowOKCancel() to return. There are problems with await and InvokeOnMainThread and this is probably for a good reason, the threads needs to be rejoined. I know under VB.Net there is a method Invoke, which is a synchronous Invocation, which is what I need here.

2) How do I pass back the result of the dialog input without using global variables. The InvokeOnMainThread parameter type requires the called function to be of type void System_Action();

Thanks.

Posts

  • adamkempadamkemp USInsider, Developer Group Leader mod

    You should research how async/await works. That's the preferred approach.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    You're not doing it right.

    Unless you are using an SDK which does not have async methods, you should use the async / await support. By default, when the async returns it's in the same context so you don't need to call something like an InvokeOnMainThread. Read this intro it's really great: https://msdn.microsoft.com/en-us/library/hh191443.aspx

    Even if you are using a SDK without async methods, you can create a wrapper with async methods. Use CancellationTokenSource.

    I don't know what is the ServerCreateCertificateCallBack, I assume you're doing a request to a backend?
    Aren't you using HttpClient for doing the request?

    You should use MVVM. Your view-models should not reference View instances.
    Also, use services:
    http://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/

    To display OK \ Cancel dialog make it like a service which returns a result.
    Xamarin Forms has a way of displaying alerts which return a result:
    var answer = await DisplayAlert ("Question?", "Would you like to play a game", "Yes", "No");
    http://developer.xamarin.com/guides/cross-platform/xamarin-forms/working-with/pop-ups/
    Unfortunately it doesn't work like a service but it's very easy to wrap it into one.

    I hope this gives you some answers on your question

  • MartHughMartHugh USMember ✭✭✭
    edited May 2015

    @AdamKemp and @AndreiNitescu many thanks for your replies.
    @AndreiNitescu I take your point about structuring I will look into this - thanks.

    Edit: On the subject of services, this code (ServerCertificateValidationCallback ) already has to be defined at the native level, so the problem is the opposite way round. This is not the cross platform level code calling into platform dependent code, this is the platform dependent code needing to call out to the UI. This does not mean that it cant be structured better to separate view from model, but it does not fit the usual Dependency Service case, If there is a way of calling out from Platform dependent code into the cross platform class and having a cross platform view class to handle dialogs, that would be ideal. Is it possible to do this?

  • MartHughMartHugh USMember ✭✭✭
    edited May 2015

    OK I have looked at this again. And I think I have a further problem with the use of async/await.

    I have a callback for a HttpClient Certificate offer, which returns a boolean based on whether the client want to go ahead with the connection or not. As I said I wanted to offer this to choice to a human user if the certificate was invalid.

    So in my AppDelegate.cs FinishedLaunching routine I setup the callback as follows :

    ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => {
        // ask the user if they want to connect 
        bool ret = AskUser(sender, cert, chain, sslPolicyErrors);
    
        return ret;
        };
    

    AskUser would prompt at the UI with a message and an OK/Cancel outcome which would return a boolean.

    My problem is that I cannot make t AskUser wait because I can't add await without adding the async keyword to the function definition. I can't do that because the function prototype for the callback is not defined by me and is not defined with async.

    I fell into the trap of assuming I could wrap this up a function call lower. In other words make AskUser async and wait within that, but that is not the way it works of course. The top level callback will run on separately and return immediately with whatever ret was initialised to on the stack.

    So, the question now is, how do I wait for something to complete from within a callback which I can't mark as async.

    Thanks.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Events shouldn't have return values anyway because events can have multiple registered handlers, and it's ambiguous which return value you should use.

    And even putting that aside, as you discovered, you can't have a function with a simple return value be asynchronous. In order to support return values in asynchronous APIs you need to return a Task<T> instead of just a T. The Task works like "promises" in other languages or APIs. It's a promise for a future value. The Task can be waited on by registering a callback to get called when the value is ready. The async/await feature is built on top of the Task API.

    What you need to do first is to restructure the code that calls this event to be able to handle an asynchronous response. If it's coming from external (non-C#) code then that means you need some kind of callback mechanism. For instance, maybe one of the arguments to the event is a function to call when the results are ready.

    It might look like this in the event handler:

    ServicePointManager.ValidateCertificate += async (sender, e) => {
        // ask the user if they want to connect 
        bool result = await AskUser(sender, e.Certificate, e.Chain, e.SslPolicyErrors);
        e.ResultCallback(result);
    };
    

    I also changed the event delegate signature to the standard C# signature, which you should use if you're doing C# programming. The convention is that event handlers have a signature like this:

    private void HandleEvent(object sender, EventArgs e)
    {
    }
    

    The EventArgs class can be extended like this:

    public class ValidateCertificateEventArgs : EventArgs
    {
        public Certificate Certificate { get; set; }
        public Action<bool> ResultCallback { get; set; }
        // ...
    }
    

    (I usually make the properties have private setters and use a constructor to initialize them, but for brevity I left that out here)

    Then you can simply define your event like this:

    public event EventHandler<ValidateCertificateEventArgs> ValidateCertificate;
    

    Calling the event would look like this:

    // For an event like this you should probably call it on the UI thread to make the handler's job easier.
    BeginInvokeOnMainThread(() =>
    {
        if (ValidateCertificate != null)
        {
            ValidateCertificate(this, new ValidateCertificateEventArgs() { Certificate, ..., result => HandleAsynchronousCertificateValidationResult(result) });
        }
    }
    

    I hope that all made sense.

  • MartHughMartHugh USMember ✭✭✭

    @adamkemp thank you. Nearly all of that does make sense.
    The one part I can't see is how you can make the callback async.

    The callback comes from Microsoft's System.Net.Security library and is defined by
    https://msdn.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback(v=vs.110).aspx

    I dont see how I can influence the firing of this event from that library, and once it does fire it will return a boolean.

    I appreciate your time replying.

  • BrianRepettiBrianRepetti USUniversity ✭✭✭

    You make the callback async by specifying the "async" keyword.

    ServicePointManager.ValidateCertificate += **async **(sender, e) => {
        // ask the user if they want to connect 
        bool result = await AskUser(sender, e.Certificate, e.Chain, e.SslPolicyErrors);
        e.ResultCallback(result);
    };
    
  • adamkempadamkemp USInsider, Developer Group Leader mod

    Ah, in that case you need to ensure that the callback is called on a background thread first (it probably is, but make sure), and then you will have to block it to wait for the result. You can do that like this:

    var callback = ValidateCertificate;
    if (callback != null)
    {
        var tcs = new TaskCompletionSource<bool>();
    
        // For an event like this you should probably call it on the UI thread to make the handler's job easier.
        BeginInvokeOnMainThread(() =>
        {
            if (callback != null)
            {
                callback(this, new ValidateCertificateEventArgs() { Certificate, ..., result => tcs.SetResult(result) });
            }
        }
    
        // This will block until the callback is called.
        return tcs.Task.Result;
    }
    

    Again, it is important that this code starts on a background thread that is safe to block. If you run this code on the UI thread and block it then you will deadlock.

  • MartHughMartHugh USMember ✭✭✭
    edited May 2015

    @breps thanks, I have already tried that. The compiler won't allow it because it differs to the Delegate.

    Error CS4010: Cannot convert async lambda expression to delegate type `System.Net.Security.RemoteCertificateValidationCallback

  • MartHughMartHugh USMember ✭✭✭

    @adamkemp Thanks. As I suspected, but hoped otherwise, it needs blocking both means other than async/wait. Will try your suggestion.

    Thanks again for your time.

  • MartHughMartHugh USMember ✭✭✭

    Thanks for your suggestions, but the dialogue appears to show very briefly and then the main form renders over the top.
    I have fed the conclusion of this thread into the wider question of getting a dialog to display from this callback in this thread which was originally to do with the UIAlertController, but all part of the same objective.

    https://forums.xamarin.com/discussion/comment/124740/#Comment_124740

    I would be very grateful for any comments.
    Thanks again.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Could you share a project that shows how this is being used? I think there are too many assumptions involved.

  • MartHughMartHugh USMember ✭✭✭

    What is the preferred way of sharing projects. I tried to upload the compressed project folder but it was 100MB and upload was refused.

  • AndrewMobileAndrewMobile USMember ✭✭✭✭

    @MartHugh GitHub !!!!!!!!!!!!!!!!

  • MartHughMartHugh USMember ✭✭✭

    Thanks
    uploaded to https://github.com/MartHugh/RepoX

    Any suggestions gratefully accepted

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Aside from github, if you delete all of the bin and obj directories and all of the directories inside the packages directory you'll find that the .zip is much smaller.

    I'll try to take a look at this later.

  • MartHughMartHugh USMember ✭✭✭

    OK thanks. I appreciate that.

  • MartHughMartHugh USMember ✭✭✭

    Another way of looking at this is to leave the user out of the decision to accept or reject an invalid certificate and instead have it set as a policy setting.

    However there would still need to be a notification to the user interface that the connect attempt failed - it just does not need to be synchronised.

    Would a better way of doing this be to raise an event in the PCL class from the native class, and then at least the correct level ViewController will be obvious in order that a dialog could be raised.

    I looked at doing this by defining an Interface going the other way so that the native classes could call back into the PCL, but the registration of the callback handle would probably have to lie with the Platform Dependency class. Whereas in order to send callbacks to the PCL class, the AppDelegate would need to know it. Making the handle a static property of the Platform Dependency class so that other classes can grab it does not seem a nice solution.

    What established methods/patterns exist to be able to call from native classes up to the PCL class?

    Thanks.

Sign In or Register to comment.