【问题标题】:Issue making calls with Azure Mobile Client in Parallel with Task.WhenAll与 Task.WhenAll 并行使用 Azure 移动客户端进行调用
【发布时间】:2016-07-29 08:13:55
【问题描述】:

我的 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

【问题讨论】:

标签: c# parallel-processing task-parallel-library azure-mobile-services


【解决方案1】:

查看source code(第307 行),您可以很容易地看到这是MobileServiceTable&lt;T&gt; 的内部设计问题,它似乎不是线程安全的(我不熟悉Xamarin)。由于您使用的是控制台应用程序,所有任务都被卸载到线程池,并行性可能(并且将)发生在第 307 行。从这一点开始,您有多个线程迭代同一个字典,而其他线程修改它。如果你再看一下MobileServiceContractResolver,你会看到一些序列化使用“id”属性作为内部使用的缓存键,它可能与代码中的“d48977fce3c6fc6b5a74c”键相同。

这不是一个完整的答案,因为 您的代码 看起来还不错。我认为这里不可能有“真正的答案”,因为即使您可能以非常过早的方式使用 async/await,您的代码也是完全有效的。我只是想您可能想知道为什么会发生这种情况。

还是

这个库有可能用于单线程上下文,例如 ASP.NET 或 UI 应用程序,而不是控制台应用程序。

【讨论】:

  • 这真的很接近答案,所以我会继续说下去。 MobileServiceClient 做了一些内部繁重的工作。它被设计为单例并用于 UI 应用程序。
  • 感谢@shay_,这很有道理。
  • @AdrianHall 在我的 Xamarin Android 应用程序中,我在启动时创建了类似 MyService 的东西作为单例。在实践中,我试图从 4 个不同的表中获取数据。并行进行这些调用会很好。你对完成这个有什么建议吗?我唯一的另一个想法是创建一个 api 控制器并通过一次调用返回所有 4 个表的数据。
  • @jalley 我认为 WhenAll 是正确的解决方案,但我不确定它为什么会导致您看到的异常。我们必须进一步调查。
  • @lindydonna-msft 这是因为MobileServiceContractResolver 不是线程安全的,而WhenAll 确实 在此示例中会导致多线程。
【解决方案2】:

(发布一个新答案,因为这是一个已修复的错误)。

您编写的客户端代码应该工作,但事实证明,它实际上是移动应用客户端 SDK 中难以重现的竞争条件的完美测试用例。在此处查看错误:https://github.com/Azure/azure-mobile-apps-net-client/issues/186

该错误已修复,但尚未发布新的 NuGet 包。 (如果有,我会更新这个答案。)同时,您可以从源代码构建自己的包以解除对您的场景的阻塞。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2016-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多