Question

I now have no problem with the People/Contacts displaying as I was having - several times in a row now they have all displayed - but the call to retrieve a Contacts object never returns - with the breakpoint on this line:

var contact = await contactPicker.PickSingleContactAsync();

...I never get there - although I am able to choose a Contact and then select the "Select" button. Control never switches from the Emulator to the breakpoint.

For full disclosure, here is the entire method:

private async Task<System.Collections.Generic.KeyValuePair<string, string>> SelectAContactForASlot()
{
    KeyValuePair<string, string> kvp;
    var contactPicker = new Windows.ApplicationModel.Contacts.ContactPicker();
    contactPicker.CommitButtonText = "Select";
    var contact = await contactPicker.PickSingleContactAsync();
    if (contact != null)
    {
        kvp = new KeyValuePair<string, string>(contact.Name, contact.Emails[0].Value);
        if (null != kvp.Value) // If there is no email, there's no use in passing it back, as the person cannot be contacted
        {
            return kvp;
        }
        else
        {
            return kvp = new KeyValuePair<string, string>(contact.Name, "No email found");
        }
    }
    return kvp = new KeyValuePair<string, string>("No Name found", "No email found");
}

So why does the call to PickSingleContactAsync() never return?

UPDATE

All the buttons of a certain functionality share an OnClick handler (I should have come into the 21.08th century and used the OnTapped handler); this handler calls the async method like so:

Task<System.Collections.Generic.KeyValuePair<string, string>> kvpContactNameEmail = SelectAContactForASlot();

UPDATE 2

Okay, now I'm really bamboozled:

This code, in the button Onclick:

private void PersonButton_OnClick(object sender, RoutedEventArgs args)
{
    Task<System.Collections.Generic.KeyValuePair<string, string>> kvpContactNameEmail = SelectAContactForASlot();
    Button btn = null;
    if (args.OriginalSource is Button)
    {
        btn = args.OriginalSource as Button;
    }
    if (null == btn)
    {
        return;
    }
    //btn1_1 to btn3_4
    if (btn.Name == "btn1_1")
    {
        ApplicationData.Current.LocalSettings.Values["PersonSection1Slot1"] = kvpContactNameEmail.Result.Key;
        btn1_1.Foreground = new SolidColorBrush(Windows.UI.Colors.Gray);
    . . .

...does get past the call to SelectAContactForASlot() -- stepping through the first line returns immediately -- but it returns before the People/Contact Picker displays. And so, there is no key or value in the KVP, since SelectAContactForASlot(), which it is returning from, is supposed to return that:

    private async Task<System.Collections.Generic.KeyValuePair<string, string>> SelectAContactForASlot()
    {
            KeyValuePair<string, string> kvp;
            var contactPicker = new Windows.ApplicationModel.Contacts.ContactPicker();
            contactPicker.CommitButtonText = "Select";
            var contact = await contactPicker.PickSingleContactAsync();
            if (contact == null)
            {
                kvp = new KeyValuePair<string, string>("No Name found", "No email found");
            }
            else // contact != null
            {
                if (string.IsNullOrWhiteSpace(contact.Emails[0].Value))
                {
                    kvp = new KeyValuePair<string, string>(contact.Name, "No email found");
                }
                else
                {
                    kvp = new KeyValuePair<string, string>(contact.Name, contact.Emails[0].Value);
                }
            }
            return kvp;
}

...and for some reason, the People/Contact picker displays (assuming I have clicked btn1_1) on reaching this line:

ApplicationData.Current.LocalSettings.Values["PersonSection1Slot1"] = kvpContactNameEmail.Result.Key;

...but still, nothing is actually returned, because it stalls on the line:

var contactPicker = new Windows.ApplicationModel.Contacts.ContactPicker();

So SelectAContactForASlot() is getting called later than I thought it would/should, but once it does get called, it fails to proceed.

UPDATE 3

    private void PersonButton_OnClick(object sender, RoutedEventArgs args)
    {
        Task<System.Collections.Generic.KeyValuePair<string, string>> kvpContactNameEmail = SelectAContactForASlot();
    . . .

    private async Task<KeyValuePair<string, string>> SelectAContactForASlot()
    {
        KeyValuePair<string, string> kvp;
        var contactPicker = new Windows.ApplicationModel.Contacts.ContactPicker();
        contactPicker.CommitButtonText = "Select";
        var contact = await contactPicker.PickSingleContactAsync();
        if (contact == null)
        {
            kvp = new KeyValuePair<string, string>("No Name found", "No email found");
        }
        else // contact != null
        {
            if (string.IsNullOrWhiteSpace(contact.Emails[0].Value))
            {
                kvp = new KeyValuePair<string, string>(contact.Name, "No email found");
            }
            else
            {
                kvp = new KeyValuePair<string, string>(contact.Name, contact.Emails[0].Value);
            }
        }
        return kvp;
    }

UPDATE 4

...and if I change my buttonClick code to:

private async void PersonButton_OnClick(object sender, RoutedEventArgs args)
{
    Task<System.Collections.Generic.KeyValuePair<string, string>> kvpContactNameEmail = await SelectAContactForASlot();
    . . .

(added "async" and "await" here), I get, "Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Threading.Tasks.Task>'"

I guess my problem really is that I don't want the call to SelectAContactForASlot() to return until it retrieves the contact object (the Contact app is invoked and the user selects a Contact); IOW, I want it to be asynchronous. But I don't think there is an async version of PickSingleContactAsync() - IOW, there's no "PickSingleContact".

So what I really want to know is: how can I invoke the Contact app and allow the user to select a Contact (already doing that) and get back values from that object that I can then access in my code? Obviously I'm going about this all wrong.

Was it helpful?

Solution

I'm guessing that at some point in your call hierarchy, you are blocking on the Task returned by SelectAContactForASlot or a method that calls it.

This will cause a deadlock because SelectAContactForASlot has to resume on the UI context when the await completes, and the UI thread is blocked waiting for SelectAContactForASlot to complete.

To avoid this deadlock, you should await the Tasks, instead of using Wait or Result, like this:

private async void PersonButton_OnClick(object sender, RoutedEventArgs args)
{
  KeyValuePair<string, string> kvpContactNameEmail = await SelectAContactForASlot();
  ...
}

I explain this scenario in full detail on my blog.

OTHER TIPS

In your Update 2 code change this line:

Task<System.Collections.Generic.KeyValuePair<string, string>> kvpContactNameEmail = SelectAContactForASlot();

for this:

Task<System.Collections.Generic.KeyValuePair<string, string>> kvpContactNameEmail = SelectAContactForASlot().AsTask();
KeyValuePair<string, string> tempKeyValuePair = FileTask.Result;

Explaining: What you're doing is binding the async world with the sync world, the original call will just get the Task but won't do anything with it, not await for it to finish (that's why your call return instantly).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top