代码契约和异步

时间:2012-02-06 17:18:14

标签: c# .net task-parallel-library code-contracts async-ctp

将后置条件添加到返回Task<T>的异步方法的推荐方法是什么?

我已阅读以下建议:

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

帖子建议将每个方法实现为同步,签约,然后将异步对应实现为简单的包装。不幸的是,我不认为这是一个可行的解决方案(也许是通过我自己的误解):

  1. 异步方法虽然被假定为同步方法的包装器,但没有任何真正的代码合同,因此可以按照自己的意愿进行。
  2. 承诺异步的代码库不太可能为所有内容实现同步对应。因此,在其他异步方法上实现包含await s的新方法因此被强制为异步。这些方法本质上是异步的,不能轻易转换为同步。它们不仅仅是包装纸。
  3. 即使我们通过说我们可以使用.Result.Wait()代替await(这实际上会导致某些SyncContext s死锁而使后一点无效,并且不管怎么说都必须用异步方法重写),我仍然坚信第一点。

    有没有其他想法,或者有什么我错过的代码合同和TPL?

3 个答案:

答案 0 :(得分:14)

我已经向Async团队指出了这一点,正如其他人所做的那样。目前,合同和异步(几乎)是互斥的。所以,至少微软的一些人都知道这个问题,但我不知道他们打算怎么做。

我不建议将异步方法编写为同步方法的包装器。事实上,我倾向于做相反的事情。

先决条件可以奏效。我最近没有尝试过;你可能需要一个包含前置条件的异步方法的小包装器。

后置条件几乎被打破了。

断言和假设确实正常,但静态检查器确实有限,因为后置条件被破坏了。

不变量在异步世界中没有那么多意义,因为可变状态往往会妨碍它。 (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 ...;
}

代码合同的一些用法是不可能的 - 例如,在接口或基类的异步成员上指定后置条件。

就个人而言,我完全避免使用我的异步代码中的合同,希望微软能在几个月内修复它。

答案 1 :(得分: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);
}

但是,这意味着“异步”方法在任务完成评估之前不会实际返回,因此“处理”将在3秒后才会打印。这类似于懒惰返回IEnumerable的方法的问题 - 合同必须枚举IEnumerable中的所有项目以确保条件成立,即使调用者实际上不会使用所有项目项目

您可以通过将合同模式更改为Preconditions来解决此问题,但这意味着实际上不会检查任何后置条件。

静态检查程序也无法将Result与lambda连接,因此您将收到“确认未经证实”的消息。 (一般来说,静态检查器无论如何都不能证明lambda / delegates的内容。)

我认为为了获得对Tasks / await的适当支持,Code Contracts团队将只有在访问Result字段时才需要特殊情况的任务添加前提条件检查。

答案 2 :(得分:0)

发布此旧帖子的新答案,因为谷歌将其作为有关CodeContract和Async的问题的第一个答案

对返回Task的异步方法的合同是否正常,并且没有必要避免它们。

异步方法的标准合同:

 Private Sub Form_Load()
       Text1.MultiLine = True
       Open "E:\Projects\VB\Ubunifu\MyList.txt" For Input As #1
       Text1.Text = Input$(LOF(1), #1)

       lblCurrent.Caption = udf_ReadLine(Text1.Text, 1)   ' read line #1
       lblCurrent_i.Caption = udf_ReadLine(Text1.Text, 2)   ' read line #2

       Close #1
    End Sub

    Private Function udf_ReadLine(ByVal sDataText As String, ByVal nLineNum As Long) As String
        Dim sText As String, nI As Long, nJ As Long, sTemp As String

        On Error GoTo ErrHandler

        sText = ""
        nI = 1
        nJ = 1
        sTemp = ""
        While (nI <= Len(sDataText))
            Select Case Mid(sDataText, nI, 1)
                Case vbCr
                    If (nJ = nLineNum) Then
                        sText = sTemp
                    End If
                Case vbLf
                    nJ = nJ + 1
                    sTemp = ""
                Case Else
                    sTemp = sTemp & Mid(sDataText, nI, 1)
            End Select
            nI = nI + 1
        Wend
        If (nJ = nLineNum) Then
            sText = sTemp
        End If
        udf_ReadLine = sText

        Exit Function

    ErrHandler:
        udf_ReadLine = ""
    End Function

如果您认为合同看起来不正确,我同意它至少会产生误导,但确实有效。并且看起来合同重写者不会过早地对任务进行评估。

正如斯蒂芬提出的一些疑问使得我的案件更多的测试和合同正确地做了他们的事情。

用于测试的代码:

[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;
    }
}