コード契約と非同期

What is the recommended way for adding postconditions to async methods which return Task?

私は次の提案を読んだ。

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

このポストでは、各メソッドを同期として実装し、それを縮小し、シンプルなラッパーとして非同期メソッドを実装することを提案しています。残念ながら、私はこれを実行可能な解決策とは考えていません(恐らく私自身の誤解によって):

  1. asyncメソッドは、syncメソッドのラッパーと見なされていますが、実際のコード契約が残っていないため、望むとおりに行うことができます。
  2. 非同期にコミットされたコードベースは、すべての同期相手を実装する可能性は低いです。その結果、他の非同期メソッドで await を含む新しいメソッドを実装すると、非同期に強制されます。これらのメソッドは、本質的に非同期であり、簡単に同期に変換することはできません。それらは単なるラッパーではありません。

await の代わりに .Result .Wait()を使用することができ、 code> SyncContext をデッドロックにする必要があります。とにかくasyncメソッドで書き直す必要があります)、私はまだ最初の点について確信しています。

他のアイデアはありますか、コード契約やTPLについて私が見逃していることはありますか?

17
MVPが間違っているとは誰も言いませんでした。
追加された 著者 Panagiotis Kanavos,

3 答え

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

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

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

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

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

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

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

その間に、あなたは仮定を書くことによって偽装 - 事後条件を持つことができます:

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

  return ...;
}

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

  return ReverseWithPostconditionAsync(s);
}

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

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

  return result;
}

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

インタフェースや基底クラスの非同期メンバの事後条件を指定するなど、コード契約の用途はまったくできません。

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

14
追加された
あなたは、「Microsoftが数カ月で修正することを望んでいた」と述べた。これを投稿したときから状況が変わったのだろうか?あなたはまだ非同期メソッドの契約を避けますか?
追加された 著者 julealgon,
@ julealgon:残念ながら、いいえ。私はまだ非同期メソッドの契約を避けます。私はまだMSがこれを修正することを望んでいます。 :)
追加された 著者 Stephen Cleary,
状況は以来変化しています。下の私の答えをチェックしてください。
追加された 著者 Gediminas Zimkus,

これを打ちましたが、 "投稿"を押すのを忘れました... :)

現時点では、これについて特別なサポートはありません。あなたができることは、( async キーワードを使わずに)次のようなものです。しかし、同じ考えです。非同期CTPの下では別の方法で動作する可能性があります。

public static Task Do()
{
    Contract.Ensures(Contract.Result>() != null);
    Contract.Ensures(Contract.Result>().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);
}

However, this means that the 'async' method won't actually return until the Task has finished evaluating, so "processing" won't be printed until 3 seconds have elapsed. This is similar to the problem with methods that lazily return IEnumerables — the Contract has to enumerate all items in the IEnumerable to ensure that the condition holds, even if the caller won't actually use all the items.

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

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

私はTasks/awaitを適切にサポートすると思いますが、Code Contractsチームは Result フィールドのアクセス時にのみ前提条件チェックを追加する特別なタスクを行う必要があります。

2
追加された
情報をありがとうございます - 私は怠惰なコレクションについても考えていませんでした: - /
追加された 著者 Lawrence Wagerfield,
Contract.ForAll コントラクトを無視するスイッチをオンにすることができます(量指定子をスキップする)。タスク(まだ)のためのそのようなスイッチはありません。
追加された 著者 porges,

CodeContractとAsyncについての最初の回答としてgoogleから返されるので、この古いスレッドに新しい回答を投稿する

Curently Taskを返す非同期メソッドの契約が正しく機能しているため、それらを避ける必要はありません。

非同期メソッドのためのStandart契約:

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


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

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

    #endregion
}

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

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

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

テストに使用されるコード:

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

}

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

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

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

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

public class Foo : IFoo
{
    public async Task 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]にある"のような契約を表現することはできません。また、実装体で表現された前提条件も期待どおりに機能しないと思います。
追加された 著者 Stephen Cleary,
それは私にとってはうまくいかない。 Task を返す非同期メソッドがあり、最初に Contract.Ensures(Contract.Result ()!= null)を記述すると、 BadImageFormatException です。
追加された 著者 piedar,