异步和等待没有“线程”?我可以自定义引擎盖下发生的事情吗?

时间:2013-06-03 14:41:41

标签: c# async-await

我有一个问题,关于如何自定义新的async / await关键字和C#4.5中的Task类。

首先了解我的问题的背景:我正在开发一个具有以下设计的框架:

  • 一个帖子有一个“当前要做的事情”列表(通常大约100到200个项目),它们存储为自己的数据结构并保存为列表。它有一个Update()函数,它枚举列表并查看某些“事物”是否需要执行并执行此操作。基本上它就像一个大线程的sheduler。为简化起见,我们假设“要做的事情”是当它们“完成”(并且不应该被称为下一次更新)时返回布尔值true的函数,并且当sheduler应该调用它们时返回false再次下次更新。
  • 所有“东西”不能同时运行,也必须在这一个线程中运行(因为线程静态变量)
  • 还有其他线程做其他事情。它们的结构方式相同:大循环,它在一个大的Update() - 函数中迭代了几个hundret事件。
  • 线程可以互相发送消息,包括“远程过程调用”。对于这些远程调用,RPC系统将某种未来对象返回到结果值。在另一个线程中,插入了一个新的“要做的事情”。
  • 要做的常见“事情”只是链接在一起的RPC序列。目前,这种“链接”的语法非常冗长和复杂,因为您必须手动检查以前的RPC的完成状态并调用下一个RPC等。

一个例子:

Future f1, f2;
bool SomeThingToDo() // returns true when "finished"
{
    if (f1 == null)
        f1 = Remote1.CallF1();
    else if (f1.IsComplete && f2 == null)
        f2 = Remote2.CallF2();
    else if (f2 != null && f2.IsComplete)
        return true;
    return false;
}

现在这一切听起来像C {5.0的asyncawait一样可以帮助我。我没有100%完全理解它在幕后做了什么(任何好的参考?),但是当我从一些我已经看过的谈话中得到它时,它正是我想要的这个非常简单的代码:

async Task SomeThingToDo() // returning task is completed when this is finished.
{
    await Remote1.CallF1();
    await Remote2.CallF2();
}

但我无法找到一种方法如何编写我的Update()函数来实现这样的事情。 asyncawait似乎想要使用Task - 类,而这似乎需要真正的线程?

到目前为止我最接近的“解决方案”:

第一个线程(正在运行SomeThingToDo)只调用它们的函数一次,并将返回的任务和测试存储在每个Update()上,无论任务是否完成。

Remote1.CallF1返回一个新的Task,其中一个空Action作为构造函数参数,并记住返回的任务。当F1实际完成时,它会在任务上调用RunSynchronously()以将其标记为已完成。

在我看来,这似乎是对任务系统的歪曲。除此之外,它会在两个线程之间创建共享内存(任务的IsComplete布尔值),如果可能的话,我希望用我们的远程消息系统替换它。

最后,它没有解决我的问题,因为它不适用于上面的类似await的SomeThingToDo实现。似乎异步函数返回的自动生成的Task对象是否立即完成?

最后我的问题是:

  1. 我可以挂钩到async / await来使用我自己的实现而不是Task<T>吗?
  2. 如果那是不可能的,我可以使用Task而没有任何与“阻塞”和“线程”相关的内容吗?
  3. 在撰写asyncawait
  4. 时,有什么好的参考

3 个答案:

答案 0 :(得分:13)

  

我还没有100%完全理解它在引擎盖下的作用 - 任何好的参考资料?

回到我们设计Mads时,Stephen和我在MSDN杂志的各个不同层面上写了一些文章。链接在这里:

http://blogs.msdn.com/b/ericlippert/archive/2011/10/03/async-articles.aspx

从我的文章开始,然后是Mads's,然后是Stephen's。

  

异步函数返回的自动生成的Task对象是否会立即完成?

不,它们在方法体中的代码返回或抛出时完成,与任何其他代码相同。

  

我可以挂钩到async / await来使用我自己的实现而不是Task<T>吗?

包含 await的方法必须返回voidTaskTask<T>。但是,只要您可以在其上调用GetAwaiter()等待的表达式就可以返回任何类型。这不一定是Task

  

如果那是不可能的,我可以使用任何没有任何与“阻塞”和“线程”相关的任务吗?

绝对。 Task只代表将来完成的工作。虽然该工作通常在另一个线程上完成,但没有要求

答案 1 :(得分:5)

  

我可以挂钩到async / await来使用我自己的实现而不是Task吗?

  

如果那是不可能的,我可以使用任何没有任何与“阻塞”和“线程”相关的任务吗?

  

当我写异步并等待时,有什么好的参考是什么?

我不鼓励你问是/否问题。你可能不只是想要是/否答案。

  

async和await似乎想要使用Task - 这反过来似乎需要真正的线程吗?

不,那不是真的。 Task表示可以在将来的某个时间点完成的事情,可能会有结果。它有时是另一个线程中某些计算的结果,但它不需要 。它可以是任何在将来的某个时刻发生的事情。例如,它可能是IO操作的结果。

  

Remote1.CallF1返回一个新的Task,其中一个空Action作为构造函数参数,并记住返回的任务。当F1实际完成时,它会在任务上调用RunSynchronously()以将其标记为已完成。

所以你在这里缺少的是TaskCompletionSource类。有了这个缺失的拼图,很多东西都应该适合。您可以创建TCS对象,将Task从它的Task属性传递给...任何人,然后使用SetResult属性来表示它已完成。这样做不会导致创建任何其他线程,也不会使用线程池。

请注意,如果您没有结果,只想要Task而不是Task<T>,那么只需使用TaskCompletionSource<bool>或其他内容,然后{{1}或者任何合适的东西。通过将SetResult(false)强制转换为Task<bool>,您可以将该实现隐藏在公共API中。

这也应该提供您提出的前两个问题的“如何”变体,而不是您提出的“我可以”版本。你可以使用Task生成一个任务,只要你说它就完成了,使用你想要的任何异步构造,这可能涉及或不涉及使用额外的线程。

答案 2 :(得分:5)

回答你的问题:

  

我可以挂钩到async / await来使用我自己的实现而不是Task吗?

是。你可以await anything。但是,我不建议这样做。

  

如果那是不可能的,我可以使用任何没有任何与“阻塞”和“线程”相关的任务吗?

Task类型代表 future 。它不一定在线程上“运行”;它可以表示下载完成或计时器到期等等。

  

当我写异步并等待时,有什么好的参考是什么?

如果你的意思是代码转换,this blog post有一个很好的并排。它的细节并不是100%准确,但它足以写一个简单的定制等待者。


如果你真的想扭转async来进行竞价,Jon Skeet的eduasync系列是最好的资源。但是,我真的不建议你在制作中这样做。

您可能会发现我的async/await intro有助于介绍async概念并推荐使用它们的方法。 official MSDN documentation也非常好。

我确实写了AsyncContext and AsyncContextThread classes,可能适合你的情况;他们为async / await方法定义了单线程上下文。您可以使用其AsyncContextThread属性将工作(或发送消息)排队到Factory