您将如何实现类似于 Async CTP await
关键字的功能?是否有一个简单的实现在所有情况下都像 await
一样工作,或者 await
是否需要针对不同的场景使用不同的实现?
最佳答案
新的 await
关键字与现有的 yield return
关键字具有相似的语义,因为它们都会导致编译器为您生成延续式状态机。因此,可以使用与异步 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
将普遍使用的协程,但以不同的方式提供它们各自的语义。
关于c# - 如何在没有异步 CTP 的情况下实现等待,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5401567/