3

I'm having an issue with my Xamarin Android app, but I believe I have narrowed the issue down to the Azure Mobile Client or my lack of understanding of Tasks in .Net. I have attempted to recreate my issue on a console app.

My goal is to make multiple api calls in parallel. In the example below I just make repeated calls to retrieve a user that does exists in the database. When I await each call (GetUsersB method) everything runs fine, but when I try to await Task.WhenAll (GetUsersA method) I almost always get an exception.

class Program
{     
    static  void Main(string[] args)
    {
        var service = new MyService();
        try
        {
            //GetUsersA(service).Wait(); //Often throws the exception attached but ocasionally is successful
            GetUsersB(service).Wait();   //Never throws an exception
        }
        catch (AggregateException e)
        {
            foreach (var ex in e.InnerExceptions)
            {
                Console.WriteLine(e.InnerException.Message);
                Console.WriteLine(e.InnerException.StackTrace);
            }
        }
        Console.WriteLine("main done");
        Console.ReadLine();
    }

    public async static Task GetUsersA(MyService service)
    {
        await Task.WhenAll(service.GetUser("d48977fce3c6fc6b5a74c"),
           service.GetUser("d48977fce3c6fc6b5a74c"),
           service.GetUser("d48977fce3c6fc6b5a74c"),             
           service.GetUser("d48977fce3c6fc6b5a74c"),
           service.GetUser("d48977fce3c6fc6b5a74c"),
           service.GetUser("d48977fce3c6fc6b5a74c"));
        Console.WriteLine("tasks complete");
    }
    public async static Task GetUsersB(MyService service)
    {
        await service.GetUser("d48977fce3c6fc6b5a74c");
        await service.GetUser("d48977fce3c6fc6b5a74c");         
        await service.GetUser("d48977fce3c6fc6b5a74c");
        await service.GetUser("d48977fce3c6fc6b5a74c");
        await service.GetUser("d48977fce3c6fc6b5a74c");
        Console.WriteLine("tasks complete");

    }
}   

if needed here is the MyService class

public class MyService
{
    private MobileServiceClient azClient;
    public MyService()
    {           
        azClient = new MobileServiceClient("https://mysite.azurewebsites.net/");           
    }

    public async Task<User> GetUser(string id)
    {
        return await azClient.GetTable<User>().LookupAsync(id);
    }
}

Here is the output of the inner exception:

Collection was modified; enumeration operation may not execute.

at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
at Microsoft.WindowsAzure.MobileServices.MobileServiceContractResolver.CreateProperties(Type type, MemberSerialization memberSerialization)
at Newtonsoft.Json.Serialization.DefaultContractResolver.CreateObjectContract(Type objectType)
at Microsoft.WindowsAzure.MobileServices.MobileServiceContractResolver.CreateObjectContract(Type type)
at Newtonsoft.Json.Serialization.DefaultContractResolver.CreateContract(Type objectType)
at Newtonsoft.Json.Serialization.DefaultContractResolver.ResolveContract(Type type)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.Linq.JToken.ToObject(Type objectType, JsonSerializer jsonSerializer)
at Newtonsoft.Json.Linq.JToken.ToObject[T](JsonSerializer jsonSerializer)
at Microsoft.WindowsAzure.MobileServices.MobileServiceSerializer.<>c__DisplayClass35_0`1.<Deserialize>b__0()
at Microsoft.WindowsAzure.MobileServices.MobileServiceSerializer.TransformSerializationException[T](Action action, JToken token)
at Microsoft.WindowsAzure.MobileServices.MobileServiceSerializer.Deserialize[T](JToken json, JsonSerializer jsonSerializer)
at Microsoft.WindowsAzure.MobileServices.MobileServiceSerializer.Deserialize[T](JToken json)
at Microsoft.WindowsAzure.MobileServices.MobileServiceTable`1.<LookupAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.WindowsAzure.MobileServices.MobileServiceTable`1.<LookupAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at TaskTest.MyService.<GetUser>d__2.MoveNext() in C:\Users\jalley\Documents\Visual Studio 2015\Projects\TaskTest\TaskTest\MyService.cs:line 24
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at TaskTest.Program.<GetUsersA>d__1.MoveNext() in C:\Users\jalley\Documents\Visual Studio 2015\Projects\TaskTest\TaskTest\Program.cs:line 31
jalley
  • 105
  • 1
  • 5
  • check this link..http://stackoverflow.com/questions/23621285/proper-use-of-task-whenall – Prashant Jul 18 '16 at 16:59
  • @Prashant this has nothing to do with his question. – shay__ Jul 18 '16 at 17:02
  • You should never use Task.Wait(), as it can cause deadlocks. Use await or WhenAll. – lindydonna Jul 18 '16 at 20:11
  • I've filed this GitHub issue, that includes a potential workaround: https://github.com/Azure/azure-mobile-apps-net-client/issues/186 – lindydonna Jul 18 '16 at 22:11
  • @lindydonna-msft thanks so much for the quick response, workaround, and by the looks of it resolution. – jalley Jul 19 '16 at 20:16
  • Yeah, the bug is still being fixed (it turns out there was a related bug). If you follow @AzureMobile on Twitter, we'll notify you of a new client SDK release. Not sure of the timeline yet. – lindydonna Jul 20 '16 at 02:33
  • Update: the bug has been fixed! You can do your own build of the client SDK while you're waiting for the official version. – lindydonna Jul 29 '16 at 00:10

2 Answers2

2

Looking at the source code (line 307) you can easily see that's an internal design issue of MobileServiceTable<T>, which appears to be not thread safe (I'm not familiar with Xamarin). Since you're using a console application, all Tasks are offloaded to the threadpool and parallelism may (and will) occur on line 307. From this point on you have multiple threads iterating the same dictionary, while other thread modifies it. If you take another look at MobileServiceContractResolver, you'll see some serializations that use "id" property as cache keys for internal usage, and it might just be the same "d48977fce3c6fc6b5a74c" key you have in your code.

This is not a complete answer since your code seems just fine. I don't think there could be a "real answer" here, since your code is perfectly valid even if you might be using async/await in a quite premature way. I just thought you might want to know why it happens.

YET

It is possible that this library was meant to be used on top of single threaded contexts, such as ASP.NET or UI apps, and not console applications.

shay__
  • 3,815
  • 17
  • 34
  • 2
    This is really close to the answer, so I'll just pile on. The MobileServiceClient does some internal heavy lifting. It's designed to be a Singleton and to be used in UI apps. – Adrian Hall Jul 18 '16 at 17:25
  • Thanks @shay_ that makes sense. – jalley Jul 18 '16 at 18:40
  • @AdrianHall In my Xamarin Android App I create something like MyService as a singleton at startup. In practice, I am trying to get data from say 4 different tables. It would be nice to make those calls in parallel. Do you have any suggestions for accomplishing this? My only other thought would be to create an api controller and return the data for all 4 tables from a single call. – jalley Jul 18 '16 at 19:06
  • @jalley I think that WhenAll is the right solution, but I'm not sure why it is causing the exceptions you're seeing. We'll have to investigate further. – lindydonna Jul 18 '16 at 20:14
  • @lindydonna-msft It is happening because `MobileServiceContractResolver` is not thread safe while WhenAll **does** cause multithreading in this example. – shay__ Jul 18 '16 at 21:04
  • @shay__ Right, and that is a bug if so, since the SDK return Tasks that are supposed to behave in the standard way. I filed an issue: https://github.com/Azure/azure-mobile-apps-net-client/issues/186, which you could perhaps link in your answer. – lindydonna Jul 18 '16 at 21:25
0

(Posting a new answer since this was a bug that has been fixed).

The client code you've written should work, but it turns out that it was actually a perfect test case for a race condition in the Mobile Apps client SDK that had been hard to repro. See the bug here: https://github.com/Azure/azure-mobile-apps-net-client/issues/186.

The bug has been fixed but a new NuGet package hasn't been released yet. (I'll update this answer when it has.) In the meantime, you can build your own package from the source to unblock your scenario.

lindydonna
  • 3,874
  • 17
  • 26
  • Thanks, actually I can wait until the update. The workaround that was provided did the trick with little effort. – jalley Jul 30 '16 at 01:58
  • Threading issue is still there: https://github.com/Azure/azure-mobile-apps-net-client/issues/264 – François Nov 22 '16 at 17:18