17

async図表1:非同期( !ではない)ネットワーク呼び出しをラップするコードTask

public static Task<byte[]> GetAsync(IConnection connection, uint id)
{
    ReadDataJob jobRDO = new ReadDataJob();

    //No overload of FromAsync takes 4 extra parameters, so we have to wrap
    // Begin in a Func so that it looks like it takes no parameters except 
    // callback and state
    Func<AsyncCallback, object, IAsyncResult> wrapped = (callback, state) =>
                jobRDO.Begin(connection, 0, 0, id, callback, state);

    return Task<byte[]>.Factory.FromAsync(wrapped, ar =>
    {
        ErrorCode errorCode;
        UInt32 sError;
        UInt32 attribute;
        byte[] data = new byte[10];
        jobRDO.End(out errorCode, out sError, out attribute, out data);
        if(error != ErrorCode.NO_ERROR)  throw new Exception(error.ToString());
        return data;
    }, jobRDO);
}

.Net 4.5をインストールすると(VSをポイントせず、再コンパイルもしない)、これは機能しなくなります。コールバックが呼び出されることはありません。

これを引き起こしている可能性のあるアイデアはありますか?それ以外の場合は、問題の根本原因をさらに絞り込んだり、回避したりするために何ができますか?

4

1 に答える 1

14

再編集StephenToubといくつかのメールを交換しました。以下では、元の回答と彼の回答を一貫性のある全体にマージしようとしています。

tl; drはこれを回避し、CompleteSynchronously常にfalseを返すように強制します(非lectorに注意してください)。


.Net4.5の「重大な」変更に関するMSDNドキュメント

この質問を投稿した直後に、関連する質問をクリックして、「。NETFramework4.5のアプリケーションの互換性」に行き着きましFromAsync

変更IAsyncResult実装は同期的に完了する必要があり 、結果のタスクを完了するには、そのCompletedSynchronouslyプロパティがtrueを返す必要があります。

影響IAsyncResult:実装が同期実行を完了しない 場合、結果のタスクは完了しませんが、そのCompletedSynchronouslyプロパティはTrueを返します。

皮肉なことに、(または腹立たしいことに)CompletedSynchronously州のページ:

実装者への注意:インターフェースのほとんどの実装者はIAsyncResultこのプロパティを使用しないため、falseを返す必要があります。


Stephen Toubは、これを次のように明確にしました。

http://msdn.microsoft.com/en-us/library/hh367887%28v=VS.110%29.aspx#coreの表 、特に「変更」の説明が間違っています(...) 。

.NET 4.5からに変更がありましたが、FromAsyncすべての IAsyncResult.CompletedSynchronously実装がtrueを返さなければならないというわけではありませんでした。それは意味がありません。変更点は、FromAsync実際にIAsyncResult’s CompletedSynchronously現在を確認することであり(.NET 4ではまったく確認しませんでした)、したがって、正確であることが期待されます。IAsyncResultそのため、バグのある実装があった場合FromAsyncでも、.NET 4で機能していた可能性がありますが、.NET 4.5では、バグのある実装で機能する可能性は低くなります。

IAsyncResult.CompletedSynchronously具体的には、を返し ても大丈夫ですfalse。ただし、が返される場合はtrueIAsyncResult実際には同期的に完了している必要があります。CompletedSynchronously返品 trueしても完了していない場合IAsyncResultは、修正が必要なバグがあり、Task返品元FromAsync が正しく完了しない可能性があります。

パフォーマンス上の理由から変更が行われました。


問題のコードに戻る

これが彼の非常に役立つ分析です。これは、他の実装者にとっておそらく役立つので、完全に含めますIAsyncResult

問題は、使用しているライブラリの実装が非常に不完全であることにあるようですIAsyncResult。特に、CompletedSynchronously正しく実装されていません。それらの実装は次のとおりです。

public bool CompletedSynchronously
{
    get { return _isCompleted; }
}
public bool IsCompleted
{
    get { return _isCompleted; }
}

それらの_isCompletedフィールドは、非同期操作が完了したかどうかを示します。これは問題あり ません。このIsCompletedプロパティは操作が完了したかどうかを示すためのものであるため、からこれを返すことは問題ありません。ただしCompletedSynchronously、同じフィールドを返すことはできません。CompletedSynchronously操作が同期的に完了したかどうか、つまり、への呼び出し中に完了したかどうかを返す必要があり、特定のインスタンス BeginXxに対して常に同じ値を返す必要があります。IAsyncResult

使用方法の標準パターンを検討してください IAsyncResult.CompletedSynchronously。その目的は、の呼び出し元がBeginXx、作業のためにコールバックを行うのではなく、後続の作業を続行できるようにすることです。これは、スタックダイブを回避するために特に重要です(すべてが実際に同期的に完了した非同期操作の長いシーケンスを想像してください。コールバックがすべての作業を処理した場合、各コールバックは次の操作を開始し、そのコールバックは次の操作を開始しますが、それらは同期的に完了し、コールバックもBeginXxメソッドの一部として同期的に呼び出されるため、各呼び出しは、オーバーフローする可能性があるまで、スタック上でますます深くなります):

IAsyncResult ar = BeginXx(…, delegate(IAsyncResult iar) =>
{
    if (iar.CompletedSynchronously) return;
    … // do the completion work, like calling EndXx and using its result
}, …);
if (ar.CompletedSynchronously)
{
    … // do the completion work, like calling EndXx and using its result
}

呼び出し元とコールバックの両方が同じ CompletedSynchronouslyプロパティを使用して、どちらがコールバックを実行するかを決定することに注意してください。そのCompletedSynchronouslyため、この特定のインスタンスに対して常に同じ値を返す必要があります。そうしないと、誤った動作が簡単に発生する可能性があります。たとえば、それらの実装は 。CompletedSynchronouslyに相当するものを返しIsCompletedます。したがって、次の一連のイベントを想像してみてください。

  • BeginXxが呼び出され、非同期操作が開始されます
  • BeginXx呼び出し元に戻ります。呼び出し元はCompletedSynchronously、操作がまだ完了していないため、falseをチェックします。
  • これで操作が完了し、コールバックが呼び出されます。コールバックはそれCompletedSynchronouslyがtrueであると認識し、呼び出し元がそれを実行したと想定するため、後続の作業は実行しません。
  • そして今、誰も実行しないか、コールバックを実行しません。

つまり、ライブラリにはバグがあります。trueを返すように変更 CompletedSynchronouslyした場合、この問題はマスクされていますが、別の問題が発生している可能性があります。呼び出し元(この場合FromAsync)が操作がすでに完了していると考えた場合、メソッドの呼び出しに進み、メソッドは次のEndXxようにブロックされます。非同期操作が完了したので、非同期操作を同期操作に変えました。CompletedSynchronously常にtrueを返すのではなく、常にfalseを返すようにしたことがありますか?

于 2013-02-15T07:18:17.357 に答える