3

XamarinのAndroidアプリに問題がありますが、問題をAzure Mobileクライアントに絞り込んだと思います。 。ネット。私はコンソールアプリケーションで自分の問題を再現しようとしました。タスクと並行してAzureモバイルクライアントを呼び出す問題.WhenAll

私の目標は、複数の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 
+0

このリンクを確認してください.http://stackoverflow.com/questions/23621285/proper-use-of-task-whenall – Prashant

+0

@Prashantこれは彼の質問とは関係ありません。 –

+0

デッドロックが発生する可能性があるため、Task.Wait()を使用しないでください。 awaitまたはWhenAllを使用します。 –

答えて

2

source code(ライン307)を見ると、あなたが簡単にそれはだ見ることができますMobileServiceTable<T>という内部設計の問題は、スレッドセーフではないようです(私はXamarinに慣れていません)。コンソールアプリケーションを使用しているので、すべてのタスクはスレッドプールにオフロードされ、並列処理は行307で発生する可能性があります。この時点から、同じディクショナリを反復する複数のスレッドがあり、他のスレッドはそれを変更します。 MobileServiceContractResolverをもう一度見てみると、 "id"プロパティを内部使用のためのキャッシュキーとして使用するシリアライゼーションがいくつか表示され、コード内にある "d48977fce3c6fc6b5a74c"キーと同じかもしれません。

コードはちょうどいいと思われるので、これは完全な答えではありません。私はあなたのコードが完全に有効なので、あなたが非常に早めの方法で非同期/待機を使用しているかもしれないので、ここに "本当の答え"があるとは思わない。私はちょうどあなたが知りたいかもしれないと思ったなぜそれが起こる。 YET

このライブラリは、このようなASP.NETやUIのアプリとしてシングルスレッド文脈の上で使用するためのものではなく、アプリケーションを慰めるた可能性があります。

+2

これは本当に答えに近いので、私はちょうど上に積み重なるでしょう。 MobileServiceClientは、いくつかの内部の重い持ち上げを行います。これは、シングルトンであり、UIアプリケーションで使用されるように設計されています。 –

+0

ありがとうございます。 – jalley

+0

@AdrianHall私のXamarin Android Appでは、起動時にMyServiceのようなものをシングルトンとして作成します。実際には、私は4つの異なるテーブルからデータを取得しようとしています。これらの呼び出しを並行して行うとよいでしょう。これを達成するための提案はありますか?私の唯一の考えは、apiコントローラを作成し、1回の呼び出しで4つのテーブルすべてのデータを返すことです。 – jalley

0

(これは修正されたバグであるため、新しい回答が投稿されています)

あなたがを書いたクライアントコードは動作するはずですが、それはそれはREPROするのは難しいされていたモバイルアプリのクライアントSDKには、実際に競合状態のための完全なテストケースだったことが判明。こちらのバグ:https://github.com/Azure/azure-mobile-apps-net-client/issues/186をご覧ください。

バグは修正されましたが、新しいNuGetパッケージはまだリリースされていません。 (私はこの回答を更新します)。その間、ソースから独自のパッケージを構築して、シナリオのブロックを解除することができます。

+0

ありがとう、実際に私は更新まで待つことができます。提供された回避策は少しの努力でトリックを行いました。 – jalley

+0

スレッドの問題はまだあります:https://github.com/Azure/azure-mobile-apps-net-client/issues/264 –

関連する問題