0

I'm trying to write a method that grabs a contact, and converts it into a data object and returns it. I understand that since the contact search is asynchronous, the calling method needs to be also; however, I want to return an object as part of the parent method that's calling the search and I'm not quite sure what the best approach is.

The pseudocode I've got at the moment is:

public Person GetRandomPerson(string s)
{
    Person myPerson = new Person();

    Person contacts = new Contacts();            

    contacts.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(contacts_SearchCompleted);
    contacts.SearchAsync(String.Empty, FilterKind.None, "All Contacts");            

    return Person;    //I'm not sure how this will ever work...
}

void contacts_SearchCompleted(object sender, ContactsSearchEventArgs e)
{
    //convert a Contact object to Person object here
}

I've done a bit of reading around the place (like this SO question) but I'm not quite sure how a nested asynchronous returning call would look like, or how I'd pass the result from the event based asynchronous contact search back onto the parent calling method - How would I achieve something to this effect?

Community
  • 1
  • 1
Henry C
  • 4,781
  • 4
  • 43
  • 83

2 Answers2

1

I changed your code a little bit. You need a shared class Person between GetRandomPerson and contacts_SearchCompleted. Look at the parameters of contacts.SearchAsync, Maybe you can pass myPerson to it without declaring as class'es private field.

Person myPerson = new Person(); //*****
public Person GetRandomPerson(string s)
{
    Person contacts = new Contacts();            

    contacts.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(contacts_SearchCompleted);
    contacts.SearchAsync(String.Empty, FilterKind.None, "All Contacts");            

    wait.WaitOne();      //*****
    return myPerson;    //I'm not sure how this will ever work...
}

ManualResetEvent wait = new ManualResetEvent(false); //*****

void contacts_SearchCompleted(object sender, ContactsSearchEventArgs e)
{
    //Fill myPerson
    wait.Set(); //*****
}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • This is kind of a singleton though - so if you call GetRandomPerson twice in quick succession, there is (however small) the chance that you're just gonna get the result of the second Person twice... – Henry C Oct 29 '11 at 12:02
  • Although I might be able to pass the Person object as the third parameter...hmm. – Henry C Oct 29 '11 at 12:04
  • also, this doesn't work, with similar problem as listed here - http://stackoverflow.com/questions/7701690/infinite-loop-when-i-combine-a-button-click-and-an-asynchronous-contacts-call i suspect the wait.one pauses the thread and never kicks back into contacts_SearchCompleted to "set" the wait...i could try pushing the contacts.SearchAsync to another thread, maybe... – Henry C Oct 29 '11 at 12:36
  • @Blakomen, I think you found the solution by yourself. – L.B Oct 29 '11 at 13:12
  • No, I just said it doesn't work with the waitone() causes issues and means the search never completes - will see if pushing some or all of the parts into another thread will help. – Henry C Oct 29 '11 at 22:19
0

Alright, I finally worked it out, and there were two parts to the solution:

1) Passing the DataObject I wanted created out of the result of the contacts search through as the third object state parameter in the .SearchAsync method call.

2) Moving the call for GetRandomPerson (which was originally just off a Button_Click) to a background thread; in this case I used ThreadPool.QueueUserWorkItem. It seems like the UI thread understandably doesn't recover very well from WaitOne().

If you're interested, the working code solution was:

private void Button_Click(object sender, RoutedEventArgs e)
{
    //this should be done asynchronously - "request" a person

    List<Person> people = new List<Person>();
    PersonService ps = new PersonService();     
    ThreadPool.QueueUserWorkItem(func =>
    {
        for (int i = 0; i < 10; i++)
        {
                    people.Add(ps.GetRandomPerson()); //you need to call this on a separate thread or it will lock the ui thread.                       
        }                                   
        Dispatcher.BeginInvoke(() => { Status.Text = "done"; });    //your button click method is now also asynchronous
    });
}

/*** Helper class ***/      

public class PersonService
{
    AutoResetEvent sync = new AutoResetEvent(false);

    public Person GetRandomPerson()
    {
        Person person = new Person();
        Contacts contacts = new Contacts();            
        contacts.SearchCompleted += new EventHandler<ContactsSearchEventArgs>(contacts_SearchCompleted);            
        contacts.SearchAsync(String.Empty, FilterKind.None, person);
        sync.WaitOne();
        return person;
    }

    void contacts_SearchCompleted(object sender, ContactsSearchEventArgs e)
    {
        Contact[] allContacts = e.Results.ToArray();
        Contact randomContact = allContacts[new Random().Next(allContacts.Length)];
        Person person = (Person)e.State;
        person.Name = randomContact.DisplayName;

        sync.Set();
    }
}

I'm still interested in hearing if anyone has a more elegant solution to this, so please shout out if you've got one!

Henry C
  • 4,781
  • 4
  • 43
  • 83
  • People (without knowing the internals of `PersonService`) calling `GetRandomPerson` directly would be blocked. This is not much different than my answer. I think, invoking `GetRandomPerson` in a thread should be handled in `PersonService` – L.B Oct 30 '11 at 17:22
  • I guess it would probably be better to move the background threading into PersonService - but I needed to push the entire call out to a background thread anyway to keep the UI thread running, so that's why I've left it as is. – Henry C Oct 30 '11 at 20:50