await
Async CTPキーワードと同様に機能するものをどのように実装しますか?await
すべての場合のように機能する単純な実装はありますか、それともawait
シナリオごとに異なる実装が必要ですか?
6 に答える
新しい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が、コルーチンを実装するための1つの一般化されたメカニズムをawait
どのように持つことができるかを示します。コルーチンはyield return
、ユビキタスに使用されますが、それぞれのセマンティクスを提供するためにさまざまな方法で使用されます。
await
常に同じ種類の変換が含まれますが、それはかなり苦痛なものです。のライブラリ側await
はそれほど複雑ではありませんが、トリッキーな点は、コンパイラがステートマシンを構築して、継続を適切な場所にジャンプできるようにすることです。
私のイテレータブロックのハッキーな使用(イールドリターン)は、似たようなものを偽造する可能性があります...しかし、それはかなり醜いでしょう。
数週間前にコンパイラが舞台裏で何をしているのかについてDevExpressウェビナーを行いました。これは、いくつかの例から逆コンパイルされたコードを示し、コンパイラが返すタスクを構築する方法と、「待機者」が何をしなければならないかを説明しています。行う。それはあなたに役立つかもしれません。
イテレータ(yield)で作成されたコルーチンの実装と例はほとんどありません。
例の1つは、非同期GUI操作にこのパターンを使用するCaliburn.Microフレームワークです。ただし、一般的な非同期コードには簡単に一般化できます。
MindTouch DReAMフレームワークは、機能的にAsync/Awaitと非常によく似たIteratorパターンの上にコルーチンを実装します。
async Task Foo() {
await SomeAsyncCall();
}
対。
IYield Result Foo() {
yield return SomeAsyncCall();
}
Result
DReAMのバージョンですTask
。フレームワークdllは.NET2.0以降で動作しますが、最近は3.5構文を多く使用しているため、フレームワークをビルドするには3.5が必要です。
MicrosoftのBillWagnerが、MSDN Magazineに、VisualStudio2010でタスク並列ライブラリを使用して非同期ctpへの依存関係を追加せずに非同期のような動作を実装する方法についての記事を書きました。
これはTask
、 C#5がリリースされると、コードがandのTask<T>
使用を開始する準備が整っているという追加の利点もあります。async
await
yield return
私の読書から、との間の主な違いは、継続に新しい値を明示的に返すことができるawait
ということです。await
SomeValue someValue = await GetMeSomeValue();
一方、を使用するとyield return
、参照によって同じことを実行する必要があります。
var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;