2012-02-06 17 views
17

Task<T>を返す非同期メソッドに事後条件を追加するための推奨方法は何ですか?私は、次の提案を読んでいるコード契約と非同期

http://social.msdn.microsoft.com/Forums/hu-HU/async/thread/52fc521c-473e-4bb2-a666-6c97a4dd3a39

をポストは、同期各方法を実施することを収縮し、次いで単純なラッパーとして非同期対応を実現示唆する。 syncメソッドのラッパーであると考えますが、任意の実際のコードの契約なしで残し、それゆえすることができますされ、非同期メソッド

  1. :残念ながら、私は(おそらく私自身の誤解を介して)実行可能な解決策としてこれを見ていませんそれは望む通りです。
  2. 非同期にコミットされたコードベースは、すべてのものに対して同期の相手を実装することはほとんどありません。その結果、他の非同期メソッドにawaitを含む新しいメソッドを実装すると、結果として非同期になります。これらのメソッドは、本質的に非同期であり、簡単に同期に変換することはできません。彼らは単にラッパーではありません。我々は.Resultまたは.Wait()代わりawaitの使用することができますと言って、後者の点を無効(実際には、いくつかのSyncContext sがデッドロックを引き起こすことになる、ととにかく非同期方式で再書かなければならない)場合でも

、私はまだ最初の点について確信しています。

代替案がありますか、コード契約やTPLについて私が見逃していることはありますか?

+1

誰もMVPのは、それが間違って取得することはできません言いませんでした。 –

答えて

14

他の人が行ったように、これをAsyncチームに指摘しました。現在、契約と非同期は(ほぼ)相互排他的です。だから、マイクロソフトの少なくとも一部の人は、この問題を認識しているが、私は彼らがそれについて何をやろうとしているのか分からない。

同期メソッドのラッパーとして非同期メソッドを記述することはお勧めしません。実際、私は逆の傾向があります。

前提条件が有効です。私は最近それを試していない。前提条件を含む非同期メソッドの周りに小さなラッパーが必要な場合があります。

事後条件はかなり壊れています。

アサーションと仮定は正常に動作しますが、事後条件が壊れているため、静的チェッカーは実際には制限されています。

インバリアントは、変更可能な状態がちょうど途切れることの多い非同期世界ではあまり意味がありません。 (非同期で、OOPから機能的なスタイルに向かって優しくあなたを押しのけます)。

VS vNextでうまくいけば、Contractsは非同期対応のポストコンディションで更新され、非同期メソッドのアサーションで静的チェッカーがうまく動作するようになります。一方

、あなたが前提と書き込むことによってふり、事後条件を持つことができます。

// Synchronous version for comparison. 
public static string Reverse(string s) 
{ 
    Contract.Requires(s != null); 
    Contract.Ensures(Contract.Result<string>() != null); 

    return ...; 
} 

// First wrapper takes care of preconditions (synchronously). 
public static Task<string> ReverseAsync(string s) 
{ 
    Contract.Requires(s != null); 

    return ReverseWithPostconditionAsync(s); 
} 

// Second wrapper takes care of postconditions (asynchronously). 
private static async Task<string> ReverseWithPostconditionAsync(string s) 
{ 
    var result = await ReverseImplAsync(s); 

    // Check our "postcondition" 
    Contract.Assume(result != null); 

    return result; 
} 

private static async Task<string> ReverseImplAsync(string s) 
{ 
    return ...; 
} 

コード契約のいくつかの用途だけでは不可能である - 例えば、インタフェースやベースクラスの非同期メンバーに事後条件を指定します。

個人的には、Microsoftが非同期コードで契約を完全に回避して、Microsoftが数カ月で修正することを期待しています。

+0

あなたは、「マイクロソフトが数カ月で修正することを望んでいた」と述べた。これを投稿したときから状況が変わったのだろうか?あなたはまだ非同期メソッドの契約を避けますか? – julealgon

+2

@ julealgon:残念ながら、いいえ。私はまだ非同期メソッドの契約を避けます。私はまだMSがこれを修正することを望んでいます。 :) –

+0

状況は以来変わっています。下の私の答えをチェックしてください。 –

2

はこれを型付きけど...「ポスト」をヒットするのを忘れて:)

このための専用サポートは現時点ではありません。あなたができる最善のは、このようなものである(asyncキーワードが、同じ考え方を使用していない - それは書き換えが非同期CTPの下で異なる動作をしますが可能です、私はまだ試していません):

public static Task<int> Do() 
{ 
    Contract.Ensures(Contract.Result<Task<int>>() != null); 
    Contract.Ensures(Contract.Result<Task<int>>().Result > 0); 

    return Task.Factory.StartNew(() => { Thread.Sleep(3000); return 2; }); 
} 

public static void Main(string[] args) 
{ 
    var x = Do(); 
    Console.WriteLine("processing"); 
    Console.WriteLine(x.Result); 
} 

しかし、これは、タスクが評価を終了するまで、 'async'メソッドは実際には戻らないため、3秒が経過するまで「処理中」が出力されないことを意味します。これは、IEnumerable s —を遅れて返すメソッドの問題に似ています。契約者はIEnumerableのすべてのアイテムを列挙して、呼び出し元が実際にすべてのアイテムを使用しなくても条件が成立するようにしなければなりません。

契約モードをPreconditionsに変更することで対処できますが、実際には後続条件はチェックされません。

静的チェッカーでもResultをラムダに接続できないため、「保証されていません」というメッセージが表示されます。 (一般的に静的チェッカーはラムダ/デリゲートに関するものをとにかく証明していません)

私はTasks/awaitを適切にサポートすると思いますが、コード契約チームは前提条件チェックのみを追加する特別なタスクResultフィールドにアクセスすると表示されます。

+0

情報をありがとうございます - 私は怠惰なコレクションについても考えていませんでした: -/ –

+0

ええ、あなたは問題を避けるために 'Contract.ForAll'契約を無視するスイッチ(量制限器をスキップ)をオンにすることができます。タスク(まだ)のためのそのようなスイッチはありません。 – porges

0

タスクを返す非同期メソッドにまま現在契約

CodeContractと非同期について質問した最初の答えが正しく機能しているし、それらを避けるために必要がないとして、それはGoogleが返されるように、この古いスレッドへの新しい答えを投稿。非同期メソッドの

スタンダール契約:

[ContractClass(typeof(ContractClassForIFoo))] 
public interface IFoo 
{ 
    Task<object> MethodAsync(); 
} 


[ContractClassFor(typeof(IFoo))] 
internal abstract class ContractClassForIFoo : IFoo 
{ 
    #region Implementation of IFoo 

    public Task<object> MethodAsync() 
    { 
     Contract.Ensures(Contract.Result<Task<object>>() != null); 
     Contract.Ensures(Contract.Result<Task<object>>().Status != TaskStatus.Created); 
     Contract.Ensures(Contract.Result<object>() != null); 
     throw new NotImplementedException(); 
    } 

    #endregion 
} 

public class Foo : IFoo 
{ 
    public async Task<object> MethodAsync() 
    { 
     var result = await Task.FromResult(new object()); 
     return result; 
    } 
} 

あなたはその契約が、私はそれが、少なくとも誤解を招くように見えますが、それは仕事をして同意しません正しく見ていないと思われる場合。そして、それは契約の書き換えが作業の早期評価を強制するように見えません。

スティーブンスはいくつか疑念を提起したので、私のケースではテストと契約が正しく行われました。テストに使用

コード:

public static class ContractsAbbreviators 
{ 
    [ContractAbbreviator] 
    public static void EnsureTaskIsStarted() 
    { 
     Contract.Ensures(Contract.Result<Task>() != null); 
     Contract.Ensures(Contract.Result<Task>().Status != TaskStatus.Created); 
    } 

} 

[ContractClass(typeof(ContractClassForIFoo))] 
public interface IFoo 
{ 
    Task<int> MethodAsync(int val); 
} 

[ContractClassFor(typeof(IFoo))] 
internal abstract class ContractClassForIFoo : IFoo 
{ 
    public Task<int> MethodAsync(int val) 
    { 
     Contract.Requires(val >= 0); 
     ContractsAbbreviators.EnsureTaskIsStarted(); 
     Contract.Ensures(Contract.Result<int>() == val); 
     Contract.Ensures(Contract.Result<int>() >= 5); 
     Contract.Ensures(Contract.Result<int>() < 10); 
     throw new NotImplementedException(); 
    } 
} 

public class FooContractFailTask : IFoo 
{ 
    public Task<int> MethodAsync(int val) 
    { 
     return new Task<int>(() => val); 
     // esnure raises exception // Contract.Ensures(Contract.Result<Task>().Status != TaskStatus.Created); 
    } 
} 

public class FooContractFailTaskResult : IFoo 
{ 
    public async Task<int> MethodAsync(int val) 
    { 
     await Task.Delay(val).ConfigureAwait(false); 
     return val + 1; 
     // esnure raises exception // Contract.Ensures(Contract.Result<int>() == val); 
    } 
} 

public class Foo : IFoo 
{ 
    public async Task<int> MethodAsync(int val) 
    { 
     const int maxDeapth = 9; 

     await Task.Delay(val).ConfigureAwait(false); 

     if (val < maxDeapth) 
     { 
      await MethodAsync(val + 1).ConfigureAwait(false); 
     } 

     return val; 
    } 
} 
+0

しかし、 "整数は範囲[5、10]にある"のような契約を表現することはできません。また、実装体で表現された前提条件も期待通りに機能しないと思います。 –

+0

それは私にとってはうまくいかない。私が 'Task 'を返す非同期メソッドを持っていて、 'Contract.Ensures(Contract.Result ()!= null)'を最初に書くと、BadImageFormatExceptionが発生します。 – piedar

関連する問題