Выполняйте вызовы с помощью мобильного клиента Azure параллельно с Task.WhenAll

c# parallel-processing task-parallel-library azure-mobile-services

192 просмотра

2 ответа

50 Репутация автора

У меня проблема с моим приложением Xamarin для Android, но я считаю, что сузил проблему до мобильного клиента Azure или из-за отсутствия понимания задач в .Net. Я попытался воссоздать свою проблему в консольном приложении.

Моя цель - сделать несколько вызовов API параллельно. В приведенном ниже примере я просто повторяю вызовы, чтобы получить пользователя, который существует в базе данных. Когда я жду каждого вызова (метод GetUsersB), все работает нормально, но когда я пытаюсь дождаться Task.WhenAll (метод GetUsersA), я почти всегда получаю исключение.

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");

    }
}   

если нужно, вот класс MyService

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);
    }
}

Вот вывод внутреннего исключения:

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 Источник Размещён: 18.07.2016 04:33

Ответы (2)


2 плюса

2349 Репутация автора

Решение

Глядя на исходный код (строка 307), вы можете легко увидеть, что это внутренняя проблема дизайна MobileServiceTable<T>, которая, по-видимому, не является поточно-ориентированной (я не знаком с Xamarin). Поскольку вы используете консольное приложение, все задачи выгружаются в пул потоков, и параллелизм может (и будет) происходить в строке 307. С этого момента у вас есть несколько потоков, повторяющих один и тот же словарь, в то время как другой поток изменяет его. Если вы еще раз посмотрите на MobileServiceContractResolver , вы увидите некоторые сериализации, которые используют свойство «id» в качестве ключей кэша для внутреннего использования, и это может быть просто тот же ключ «d48977fce3c6fc6b5a74c», который есть в вашем коде.

Это не полный ответ, так как ваш код выглядит просто отлично. Я не думаю, что здесь может быть «реальный ответ», так как ваш код совершенно корректен, даже если вы используете async / await довольно преждевременно. Я просто подумал, что вы можете знать, почему это происходит.

ВСЕ ЖЕ

Возможно, эта библиотека предназначалась для использования поверх однопоточных контекстов, таких как приложения ASP.NET или пользовательского интерфейса, а не консольных приложений.

Автор: shay__ Размещён: 18.07.2016 05:17

0 плюса

3814 Репутация автора

(Публикация нового ответа, поскольку эта ошибка была исправлена).

Код клиента, который вы написали, должен работать, но оказывается, что это был идеальный тестовый случай для состояния гонки в SDK клиента Mobile Apps, который было сложно воспроизвести. Смотрите ошибку здесь: https://github.com/Azure/azure-mobile-apps-net-client/issues/186 .

Ошибка была исправлена, но новый пакет NuGet еще не выпущен. (Я обновлю этот ответ, когда он появится.) А пока вы можете создать свой собственный пакет из исходного кода, чтобы разблокировать ваш сценарий.

Автор: lindydonna Размещён: 29.07.2016 12:13
Вопросы из категории :
32x32