您如何实现与Async CTP await
关键字类似的功能?是否有一个简单的实现在所有情况下都像await
一样,或者await
是否需要针对不同场景的不同实现?
答案 0 :(得分:9)
新的await
关键字与现有的yield return
关键字具有相似的语义,因为它们都会使编译器为您生成连续样式状态机。因此,可以使用与Async CTP具有一些相同行为的迭代器来一起破解某些东西。
这就是它的样子。
public class Form1 : Form
{
private void Button1_Click(object sender, EventArgs e)
{
AsyncHelper.Invoke<bool>(PerformOperation);
}
private IEnumerable<Task> PerformOperation(TaskCompletionSource<bool> 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<T> Invoke<T>(Func<TaskCompletionSource<T>, IEnumerable<Task>> method)
{
var context = SynchronizationContext.Current;
var tcs = new TaskCompletionSource<T>();
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 如何有一个通用机制来实现协同程序,await
和yield return
将无处不在地使用它们,但是以不同的方式提供它们各自的语义。
答案 1 :(得分:8)
await
总是涉及同样的转变 - 但这是一个非常痛苦的转变。 await
的库方面并不复杂,但棘手的是编译器为您构建状态机,允许继续跳回到正确的位置。
可能我的一些hacky使用迭代器块(产量返回)你可以伪造类似的东西......但它会非常难看。
我给了一个DevExpress webinar on what the compiler is doing behind the scenes a few weeks ago - 它显示了几个例子的反编译代码,以及解释编译器如何构建要返回的任务,以及“awaiter”必须做什么。它可能对你有用。
答案 2 :(得分:2)
用迭代器(yield)做的协程的实现和示例很少。
其中一个示例是Caliburn.Micro框架,它使用此模式进行异步GUI操作。但它可以很容易地推广为一般的异步代码。
答案 3 :(得分:2)
MindTouch DReAM框架在Iterator模式之上实现了Coroutines,它在功能上与Async / Await非常相似:
async Task Foo() {
await SomeAsyncCall();
}
VS
IYield Result Foo() {
yield return SomeAsyncCall();
}
Result
是DReAM的Task
版本。框架dll适用于.NET 2.0+,但要构建它需要3.5,因为我们现在使用了很多3.5语法。
答案 4 :(得分:1)
它广泛使用Task
和Task<T>
,这也带来了额外的好处,一旦C#5退出,您的代码就会做好充分准备,开始使用async
和await
答案 5 :(得分:0)
根据我的阅读,yield return
和await
之间的主要区别在于await
可以明确地向续集中返回一个新值。
SomeValue someValue = await GetMeSomeValue();
而对于yield return
,你必须通过引用来完成同样的事情。
var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;