17

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

我已阅读以下建议:

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

该帖子建议将每个方法实现为同步,将其收缩,然后将异步对应物实现为简单的包装器。不幸的是,我不认为这是一个可行的解决方案(也许是由于我自己的误解):

  1. async 方法虽然被假定为 sync 方法的包装器,但它没有任何真正的代码协定,因此可以随心所欲。
  2. 致力于异步的代码库不太可能为所有内容实现同步对应项。结果,await在其他异步方法上实现包含 s 的新方法因此被迫异步。这些方法本质上是异步的,不能轻易转换为同步的。它们不仅仅是包装器。

即使我们通过说我们可以使用.Resultor.Wait()而不是来使后一点无效await(这实际上会导致一些SyncContexts 死锁,并且无论如何都必须在 async 方法中重新编写),我仍然相信第一点。

是否有任何替代想法,或者我错过了关于代码合同和 TPL 的任何内容?

4

3 回答 3

14

正如其他人所做的那样,我已经向 Async 团队指出了这一点。目前,Contracts 和 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 ...;
}

代码契约的某些用法是不可能的——例如,在接口或基类的异步成员上指定后置条件。

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

于 2012-02-07T01:53:10.377 回答
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 秒后才打印。这类似于延迟返回IEnumerables 的方法的问题——合约必须枚举 s 中的所有项目IEnumerable以确保条件成立,即使调用者实际上不会使用所有项目。

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

静态检查器也无法将Resultlambda 与 lambda 连接,因此您将收到“确保未经证实”的消息。(一般来说,静态检查器无论如何都不能证明关于 lambdas/delegates 的事情。)

我认为为了获得对 Tasks/await 的适当支持,代码合同团队将不得不对特殊情况下的 Tasks 进行仅在访问该Result字段时添加前置条件检查。

于 2012-02-07T11:01:39.500 回答
0

将新答案发布到这个旧线程,因为它由谷歌返回作为关于 CodeContract 和 Async 问题的第一个答案

目前,返回 Task 的异步方法上的 Contract 工作正常,无需避免它们。

异步方法的标准合约:

[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;
    }
}
于 2016-11-21T10:14:43.447 回答