Xamarin Async method not working moving it to its own class

I am trying to clean up my code and put things in classes (something I should have done from the start, I know). But I am running into a problem with an async Task.

I have the following async method, which was originally in the same class as the call to that method, i.e no instantiation needed, I just called the method, it ran asynchronously, and all worked fine.

However, I have now moved this async method to a new class, where I intend to put all methods relating to the database. So, now I create an instance of the new class and call the method, but it seems to get hung up and not go anywhere.

Here is the new class with the asnyc method:

public class ParseDBQuery
{
    public string DBcompanyName { get; set; }
    public string DBofferTitle { get; set; }
    public string DBlatitude { get; set; }
    public string DBlongitude { get; set; }
    public string DBofferDescription { get; set; }
    public string DBlogoURL { get; set; }

    public async Task getUserCoupons()
    {
        try{

            var query = ParseObject.GetQuery("userCoupons").WhereEqualTo("userObjectID", ParseUser.CurrentUser).Include("couponsObjectID");

            IEnumerable<ParseObject> MyResults =  await query.FindAsync();

            foreach (var result in MyResults)
            {
                var couponObject = result.Get<ParseObject>("couponsObjectID");

                DBcompanyName = couponObject.Get<string>("entityName");
                Console.WriteLine ("The company name is......... " + DBcompanyName);

                DBofferTitle = couponObject.Get<string>("offerTitle");
                Console.WriteLine ("The offer title is......... " + DBofferTitle);

                DBofferDescription = couponObject.Get<string>("offerDescription");
                Console.WriteLine ("The offer title is......... " + DBofferDescription);

                DBlogoURL = couponObject.Get<string>("logoURL");
                Console.WriteLine ("The logo URL is......... " + DBlogoURL);

                DBlatitude = couponObject.Get<string>("latitude");
                Console.WriteLine ("The latitude is......... " + DBlatitude);

                DBlongitude = couponObject.Get<string>("longitude");
                Console.WriteLine ("The longitude is......... " + DBlongitude);

            }

        }
        catch (ParseException e) {
            Console.WriteLine ("There was a problem fetching getUserCoupon data from parse - ParseDBQuery class");

        }

    }
}

Here is the instantiation and call to the method:

ParseDBQuery myQuery = new ParseDBQuery ();
Task myTask = myQuery.getUserCoupons ();
//myTask.Wait ();

Here you can see i've tried the Wait() method, and this is where it stops. If i remove the Wait() method, the code carries on before getting values from the async method (obviously because of await). The problem seems to be in the new class where the method now lives. If I put a breakpoint on one of the queries such as

DBcompanyName = couponObject.Get<string>("entityName");

... it never hits the breakpoint. So the problem lies here somewhere but I don't know why.

The odd thing is, if i just put the whole method back into the same class as the method call, without instantiation, it works perfectly! Any ideas?

Upvotes: 0

Views: 925

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457382

This is the common deadlock situation that I describe on my blog.

When an async method awaits a task, by default the await will capture a "current context" and use that to resume the async method. In this case, that context is the UI context, which is associated with a single UI thread. When your code blocks on the task (by calling Wait), it is blocking the UI thread. When the FindAsync task completes, the async getUserCoupons method attempts to resume on the UI thread, but it can't because the UI thread is blocked.

As an aside, it works when the code is in the Main class (when your code presumably called Wait on the task returned from FindAsync) because FindAsync doesn't resume on the captured context.

The ideal solution is to use await instead of Wait. This means that the method calling getUserCoupons must become async, and its caller should use await and become async, etc., etc. This growth of async is perfectly natural and should be embraced. Ideally, you want to go "async all the way"; I describe this concept more in an MSDN article.

Upvotes: 1

Related Questions