如何在没有Async CTP的情况下实现等待

您将如何实现与Async CTP await关键字类似的function? 是否存在一个简单的实现,在所有情况下都像await一样,或者是否需要针对不同场景的不同实现?

await总是涉及同样的转变 – 但这是一个非常痛苦的转变。 await侧不是太复杂,但棘手的是编译器为您构建状态机,允许继续跳回到正确的位置。

可能会使用迭代器块(产量返回),你可以伪造类似的东西……但它会非常丑陋。

几周前我给出了一个关于编译器在幕后做什么的DevExpress网络研讨会 – 它显示了几个例子中的反编译代码,以及解释编译器如何构建要返回的任务,以及“awaiter”具有什么做。 它可能对您有用。

新的await关键字与现有的yield return关键字具有相似的语义,因为它们都会使编译器为您生成延续样式状态机。 因此,可以使用与Async CTP具有一些相同行为的迭代器来一起破解某些东西。

这是它的样子。

 public class Form1 : Form { private void Button1_Click(object sender, EventArgs e) { AsyncHelper.Invoke(PerformOperation); } private IEnumerable PerformOperation(TaskCompletionSource tcs) { Button1.Enabled = false; for (int i = 0; i < 10; i++) { textBox1.Text = "Before await " + Thread.CurrentThread.ManagedThreadId.ToString(); yield return SomeOperationAsync(); // Await textBox1.Text = "After await " + Thread.CurrentThread.ManagedThreadId.ToString(); } Button2.Enabled = true; tcs.SetResult(true); // Return true } private Task SomeOperationAsync() { // Simulate an asynchronous operation. return Task.Factory.StartNew(() => Thread.Sleep(1000)); } } 

由于yield return生成一个IEnumerable我们的协同程序必须返回一个IEnumerable 。 所有的魔法都发生在AsyncHelper.Invoke方法中。 这就是让我们的协程(伪装成黑客迭代器)的原因。 需要特别注意确保迭代器始终在当前同步上下文中执行,如果存在迭代器,则在尝试模拟await在UI线程上的工作方式时非常重要。 它通过同步执行第一个MoveNext ,然后使用SynchronizationContext.Send从工作线程执行其余工作来完成此操作,工作线程也用于异步等待各个步骤。

 public static class AsyncHelper { public static Task Invoke(Func, IEnumerable> method) { var context = SynchronizationContext.Current; var tcs = new TaskCompletionSource(); var steps = method(tcs); var enumerator = steps.GetEnumerator(); bool more = enumerator.MoveNext(); Task.Factory.StartNew( () => { while (more) { enumerator.Current.Wait(); if (context != null) { context.Send( state => { more = enumerator.MoveNext(); } , null); } else { enumerator.MoveNext(); } } }).ContinueWith( (task) => { if (!tcs.Task.IsCompleted) { tcs.SetResult(default(T)); } }); return tcs.Task; } } 

关于TaskCompletionSource的全部内容是我尝试复制await可以“返回”一个值的方式。 问题是协程必须实际返回一个IEnumerable因为它只不过是一个被黑客攻击的迭代器。 所以我需要提出一种替代机制来捕获返回值。

这有一些明显的局限性,但我希望这能给你一般的想法。 它还演示了CLR如何具有一种用于实现协程的通用机制, awaityield return将无处不在地使用,但是以不同的方式提供它们各自的语义。

用迭代器(yield)做的协程的实现和示例很少。

其中一个例子是Caliburn.Micro框架,它使用这种模式进行异步GUI操作。 但它很容易推广到一般的异步代码。

MindTouch DReAM框架在Iterator模式之上实现了Coroutines,它在function上与Async / Await非常相似:

 async Task Foo() { await SomeAsyncCall(); } 

 IYield Result Foo() { yield return SomeAsyncCall(); } 

Result是DReAM的Task版本。 框架dll可以与.NET 2.0+一起使用,但要构建它需要3.5,因为我们现在使用了很多3.5语法。

微软的Bill Wagner 在MSDN杂志上写了一篇关于如何使用Visual Studio 2010中的任务并行库来实现异步行为而不添加对异步ctp的依赖的文章。

它广泛使用TaskTask ,这也有一个额外的好处,一旦C#5出来,你的代码将准备好开始使用asyncawait

从我的阅读来看, yield returnawait之间的主要区别在于await可以明确地向延续中返回一个新值。

 SomeValue someValue = await GetMeSomeValue(); 

而对于yield return ,你必须通过引用完成同样的事情。

 var asyncOperationHandle = GetMeSomeValueRequest(); yield return asyncOperationHandle; var someValue = (SomeValue)asyncOperationHandle.Result;