您将如何实现与 Async CTPawait
关键字类似的功能?是否有一个await
在所有情况下都可以工作的简单实现,或者是否await
需要针对不同场景的不同实现?
6 回答
newawait
关键字与现有关键字具有相似的语义,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
普遍使用这些协同程序,但以不同的方式提供它们各自的语义。
await
总是涉及到同样的转变——但这是一个相当痛苦的转变。库方面并await
不太复杂,但棘手的是编译器为您构建了一个状态机,允许继续跳回正确的位置。
有可能我对迭代器块的一些hacky使用(收益回报)你可以伪造类似的东西......但这会很丑陋。
几周前,我举办了一次DevExpress 网络研讨会,讨论编译器在幕后所做的事情——展示了几个示例中的反编译代码,并解释了编译器如何构建要返回的任务,以及“等待者”必须做什么做。它可能对你有用。
很少有由迭代器(yield)组成的协同程序的实现和示例。
其中一个例子是 Caliburn.Micro 框架,它使用这种模式进行异步 GUI 操作。但它可以很容易地推广到一般的异步代码。
MindTouch DReAM框架在 Iterator 模式之上实现了 Coroutines,它在功能上与 Async/Await 非常相似:
async Task Foo() {
await SomeAsyncCall();
}
对比
IYield Result Foo() {
yield return SomeAsyncCall();
}
Result
是 DReAM 的版本Task
。框架 dll 可与 .NET 2.0+ 一起使用,但要构建它,您需要 3.5,因为这些天我们使用了大量的 3.5 语法。
来自 Microsoft 的 Bill Wagner在 MSDN 杂志上写了一篇文章,介绍了如何使用 Visual Studio 2010 中的任务并行库来实现类似异步的行为,而无需添加对异步 ctp 的依赖。
它使用Task
和Task<T>
广泛使用还有一个额外的好处,即一旦 C# 5 出来,您的代码将准备好开始使用async
和await
.
yield return
从我的阅读来看,和之间的主要区别在于await
可以await
显式地将新值返回到延续中。
SomeValue someValue = await GetMeSomeValue();
而使用yield return
,您必须通过引用来完成相同的事情。
var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;