3

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.

Community
  • 1
  • 1
B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
  • Are you Waiting on the call to this method? – fsimonazzi Nov 22 '12 at 18:21
  • What I meant to ask is whether at some point you're invoking the Wait() method on the returned task. If you're initiating this async operation from the UI thread and you're waiting on the task, the continuation for the call to contactPicker.PickSingleContactAsync() will not have where to be executed because the UI thread is blocked waiting. – fsimonazzi Nov 22 '12 at 19:11
  • No, I haven't got a Wait() anywhere. – B. Clay Shannon-B. Crow Raven Nov 22 '12 at 21:53

2 Answers2

3

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I don't think I'm doing that; I've appended pertinent info/code to my original post above. – B. Clay Shannon-B. Crow Raven Nov 22 '12 at 22:13
  • Okay, thanks for looking at this - I'm posting more code and my debug (stepping though) findings above. – B. Clay Shannon-B. Crow Raven Nov 22 '12 at 22:59
  • As I suspected, you are using `Result`. Please read through my answer and apply the solution I specified (use `await` instead of `Wait` or `Result`). – Stephen Cleary Nov 23 '12 at 02:00
  • I still don't know what you mean; figuring you were talking about the "Result" in this line: ApplicationData.Current.LocalSettings.Values["PersonSection1Slot1"] = kvpContactNameEmail.Result.Key; ...I tried to change it to: ApplicationData.Current.LocalSettings.Values["PersonSection1Slot1"] = await kvpContactNameEmail.Key; ...but Key is not available that way. The closest I could get was this: ApplicationData.Current.LocalSettings.Values["PersonSection1Slot1"] = kvpContactNameEmail.AsAsyncOperation().GetResults().Key; ...but that crashes. – B. Clay Shannon-B. Crow Raven Nov 23 '12 at 16:22
  • `(await kvpContractNameEmail).Key` – Stephen Cleary Nov 23 '12 at 16:25
  • I guess I will take a break from this and come back to it later; I read your blog post, which I found interesting, but I still don't know how to do this (obviously), and I don't know what code "(await kvpContactNameEmail).Key" should replace. FWIW, my latest code is in Update 3 above. I'm going to add a bounty to this question when possible (tomorrow); if I get a solution prior to that, I will award a bounty anyway. – B. Clay Shannon-B. Crow Raven Nov 23 '12 at 19:16
  • And now that I've bountified it, I can't actually award the bounty until tomorrow. – B. Clay Shannon-B. Crow Raven Nov 24 '12 at 20:22
0

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).

Rafael
  • 2,827
  • 1
  • 16
  • 17
  • That fails with "Instance argument: cannot convert from 'System.Threading.Tasks.Task>' to 'Windows.Foundation.IAsyncAction'" and "'System.Threading.Tasks.Task>' does not contain a definition for 'AsTask' and the best extension method overload 'System.WindowsRuntimeSystemExtensions.AsTask(Windows.Foundation.IAsyncAction)' has some invalid arguments" and "The name 'FileTask' does not exist in the current context" – B. Clay Shannon-B. Crow Raven Nov 23 '12 at 21:27
  • Even if `Task` did have an extension method `AsTask`, this would still deadlock because it uses `Result`. – Stephen Cleary Nov 23 '12 at 21:36