1

我正在尝试扩展一个框架,我们的框架用户可以使用 C# 作为具有非阻塞函数(又名“协程”)的语言。我的第一个设计使用yield return语句和 IEnumerator 作为函数返回值。

但是,如果您尝试调用另一个产生函数(如果另一个函数应该返回一个值,则更是如此),这很容易出错并且使用起来很尴尬。

所以我在玩弄使用asyncawait提供 corountines 的想法。生成的语法会更有趣。

在我们的框架中,我需要确保这些非阻塞脚本中的任何一个都不会并行运行,因此我编写了自己的脚本来自行SynchonizationContext安排所有操作。这就像一个魅力。

现在更有趣的东西:我们设计的另一个核心部分是,用户可以注册某种“检查功能”,如果它们失败,则不会继续“当前”执行功能。每次非阻塞功能恢复时都应执行检查功能。

例如:

async Task DoSomething()
{
    var someObject = SomeService.Get(...);
    using (var check = new SanityCheckScope())
    {
        check.StopWhen(() => someObject.Lifetime < 0);

        ...

        await SomeOtherStuff();
        // when execution comes back in here, the StopWhen - condition above should be
        // evaluated and the Task should not be scheduled if its true.

        ...
    }
}

每当SomeOtherStuff或任何由它调用的异步函数恢复时,都应检查注册的条件,DoSomething如果任何条件为真,则应停止。

为了实现这一点,我的 SynchronizationContext 检查已注册的函数(通过CallContext.LogicalSetData),如果返回 true,则不安排任务。

现在来了棘手的问题。假设函数SomeOtherStuff如下所示:

async Task SomeOtherStuff()
{
    using (var check = new SanityCheckScope())
    {
        // register my own check functions
        await Whatever();
    }
}

在示例中SomeOtherStuff注册了自己的检查功能。如果它们在 之后为真await Whatever(),自然SomeOtherStuff应该只停止该功能。(假设如果 SomeOtherStuff 返回 a Task<XXX>,那么如果default(XXX)将其用作返回值对我来说就可以了。)

我怎么能接近这个?我无法访问await SomeOtherStuff();原始函数中的操作。任何一个的开头似乎都没有钩子或回调await(无论如何也没有意义)。

另一个想法是抛出异常而不是“不调度”任务。相反(或附加于)using-block,我会写一个 try/catch。但是,然后我遇到的问题是,如果内部的任何检查功能 DoSomething内部失败后await Whatever();我将如何停止DoSomething(连同SomeOtherStuff)?

因此,在我从头开始所有这些并回到我的IEnumeratoryield return.. 之前,有人知道这是否可以由async/await框架完成吗?

4

2 回答 2

2

我的建议是只使用 TPL Dataflow 并完成它。您滥用语言功能async(或IEnumerable就此而言)越多,您的代码的可维护性就越差。

那就是说...

你确实(有点)有一些钩子await:自定义等待对象。Stephen Toub 有一个关于它们的博客,Jon Skeet 在他的eduasync 系列中介绍了一些细节。

但是,您不能从async方法返回自定义可等待对象,因此这种方法意味着您的所有awaits 都必须“选择加入”“检查”行为:

async Task DoSomething()
{
  var someObject = SomeService.Get(...);
  using (var check = new SanityCheckScope())
  {
    check.StopWhen(() => someObject.Lifetime < 0);
    await SomeOtherStuff().WithChecks();
  }
}

我不清楚检查失败时的语义应该是什么。从您的问题来看,听起来一旦检查失败,您只想中止该方法,因此检查更像是代码合同,而不是监视器或条件变量。听起来范围应该只应用于该方法,而不是从该方法调用的方法。

您可以通过使用封装返回Task并用于TaskCompletionSource<T>公开不同Task. 自定义等待者将在执行该方法的继续之前执行检查async

另一件需要注意的是你的“范围”是如何工作的。考虑这种情况:

async Task DoSomething()
{
  using (var check = new SanityCheckScope())
  {
    check.StopWhen(...);

    await SomeOtherStuff();

    await MoreOtherStuff();
  }
}

或者更有趣:

async Task DoSomething()
{
  using (var check = new SanityCheckScope())
  {
    check.StopWhen(...);

    await SomeOtherStuff();

    await Task.WhenAll(MoreOtherStuff(), MoreOtherStuff());
  }
}

我怀疑您的范围必须使用AsyncLocal<T>(在我的博客中描述)才能正常工作。

于 2013-06-11T11:47:21.267 回答
0

你有没有考虑实施你自己的SynchronizationContext?您可以实现自己的(假设尚未安装有意义的 SynchronizationContext),也可以围绕现有上下文安装包装器。

await 语法会将延续发布到 SynchronizationContext(假设 await 未配置为这样做),这将允许您拦截它。当然,任何异步回调都将发布到 SynchronizationContext,因此可能无法直接检测到调用延续的确切时间,因此这可能会导致对发布到上下文的所有回调进行完整性检查。

您的 SanityCheckScope 可以确保检查范围以嵌套方式注册到您的 SynchronizationContext 中,显然,您的类的 IDisposable 部分取消注册并恢复父范围。这仅在您没有可以拥有多个并行子范围的情况下才有效。

据我所知,停止执行的唯一方法是抛出某种异常。

于 2013-06-11T10:09:39.803 回答