2012-03-06 16 views
2

私はこのような何かをするいくつかのコードを持っている:再帰WinRTの非同期の問題は

abstract class Data 
{ 
    Data(string name, bool load) { if (load) { Load().Wait(); } 
    abstract Task Load(); 
} 

class XmlData : Data 
{ 
    XmlData(string name, bool load = true) : base(name, load) {} 
    override async Task Load() 
    { 
     var file = await GetFileAsync(...); 
     var xmlDoc = await LoadXmlDocAsync(file); 
     ProcessXml(xmlDoc); 
    } 
    void ProcessXml(XmlDocument xmlDoc) 
    { 
     foreach (var element in xmlDoc.Nodes) 
     { 
      if (element.NodeName == "something") 
       new XmlData(element.NodeText); 
     } 
    } 
} 

私はそれがGetFileAsync(...)でコードをぶら下げ終わる場所(時々)、奇妙タイミングの問題を取得するように見えます。これは呼び出しの再帰的性質によって引き起こされますか?私はすべての待っている呼び出しを変更して実際に.Wait()を行って終了させ、本質的にすべての非同期呼び出しを取り除くと、私のコードは正常に実行されます。

+0

私はデバッガを接続してブレークすると、ただ座って、Load.Wait()を待っています。私は今、私にそれを持っていません。しかし、私が覚えている限り、何も実行されていませんでした。それはロードを待っていただけでしたが、他に何も起こっていないようでした。 –

+0

@gamernbそうであれば、デッドロックがキャプチャされたコンテキストで待機することはほとんど保証されます。私の答えを見てください。 –

+1

コンストラクタで仮想メソッドを呼び出すことは、一般的には悪い考えです(http:// stackoverflowを参照してください)。com/a/448272/224370など) –

答えて

3

これは呼び出しの再帰的な性質によるものですか?私はすべての待っている呼び出しを変更して実際に.Wait()を行って終了させ、本質的にすべての非同期呼び出しを取り除くと、私のコードは正常に実行されます。

それは本当に依存 - あなたの呼び出し側が何らかの形で(、)(待って呼び出し経由など)をユーザー・インターフェース・スレッドをブロックしている場合

最も可能性の高い犯人だろう。この場合、待機するデフォルトの動作は、呼び出し元の同期コンテキストを取得し、そのコンテキストに結果を戻すことです。

ただし、呼び出し元がそのコンテキストを使用している場合、デッドロックが発生する可能性があります。

これは非常に可能性の高いケースで、このコード行によって引き起こされている:

Data(string name, bool load) { if (load) { Load.Wait(); } 

これは簡単に(このXMLDATAクラスのように)あなたのライブラリーのコードを作ることによって回避することができ、明示的にないは、呼び出しを使用します同期コンテキスト。これは通常、ユーザーインターフェイスコードでのみ必要です。キャプチャを避けることによって、あなたは2つのことを行います。まず、全体的なパフォーマンスを向上させる(しばしば劇的に改善する)こと、そして次に、このデッドロック状態を回避することです。

これはConfigureAwaitを使用し、そのようなあなたのコードを変更することで行うことができます。

override async Task Load() 
{ 
    var file = await GetFileAsync.(...).ConfigureAwait(false); 
    var xmlDoc = await LoadXmlDocAsync(file).ConfigureAwait(false); 
    ProcessXml(xmlDoc); 
} 

言われていること - 私はこのデザインを少し考え直すでしょう。ここには本当に2つの問題があります。

まず、コンストラクタに仮想メソッド呼び出しを入れます。これはかなり危険です。可能な場合は避けてください。異常な問題が発生する可能性があります。

第2に、非同期操作全体を同期操作に変えるには、これをコンストラクターにブロックで入れます。代わりに、この全体を再考することをお勧めします。

おそらく、あなたはデータを非同期にロードして戻す何らかの形式の工場を作るためにこれを再加工することができますか?これは、作成のためのパブリックAPIをTask<Data>、またはpublic async Task<TData> Create<TData>(string name) where TData : Dataという一般的なメソッドを返すファクトリメソッドと同じくらい単純なものにすることができます。これにより、非同期の構築とロードを維持し、ブロックを完全に回避できます。

+0

2番目の行は同期コンテキストなしで実行されるため、最初の 'Task'で' ConfigureAwait() 'を呼び出すだけで十分だと思います。しかし、このようにすることは、より堅牢である可能性があります(たとえば、後で最初の操作を同期して実行することにした場合)。 – svick

+0

@svick True - ほとんど。私はこれが好きです。あなたがその動作を変更するかもしれない2つの間に何かを導入すればもっと簡単です...また、 'GetFileAsync'がすでに完了して同期的に実行されている場合(つまり、キャッシュされたタスクを使用している場合) ConfigureAwait'はまだ必要です。 –

+0

@svickそれは、常に同期して完了し、状態を決して変更できないため、最初のタスクがコンテキストを変更するという保証がないからです。 –