Frage

Wie würden Sie etwas implementieren, das ähnlich wie das Schlüsselwort await von Async CTP funktioniert?Gibt es eine einfache Implementierung, die in allen Fällen wie await funktioniert, oder erfordert await unterschiedliche Implementierungen für unterschiedliche Szenarien?

War es hilfreich?

Lösung

await beinhaltet immer die gleiche Art von Transformation - aber es ist ziemlich schmerzhaft.Die Bibliothek -Seite von await ist nicht allzu kompliziert, aber das Knifflige ist, dass der Compiler eine Zustandsmaschine für Sie erstellt, sodass die Fortsetzung an die richtige Stelle zurückspringen kann.

Es ist möglich , dass Sie bei meiner hackigen Verwendung von Iteratorblöcken (Yield Return) etwas Ähnliches vortäuschen könnten ... aber es wäre ziemlich hässlich.

Ich habe vor einigen Wochen ein DevExpress-Webinar darüber gegeben, was der Compiler hinter den Kulissen tut -Das zeigt dekompilierten Code aus einigen Beispielen und erklärt, wie der Compiler eine Aufgabe erstellt, die zurückgegeben werden soll, und was der "Kellner" zu tun hat.Es kann für Sie nützlich sein.

Andere Tipps

Das neue Schlüsselwort await hat eine ähnliche Semantik wie das vorhandene Schlüsselwort yield return, da beide den Compiler veranlassen, die Zustandsmaschine für den Fortsetzungsstil für Sie zu generieren. Es ist also möglich, etwas mit Iteratoren zusammen zu hacken, die sich ähnlich verhalten wie das Async CTP.

So würde es aussehen.

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));
    }
}

Da yield return einen IEnumerable generiert, muss unsere Coroutine einen IEnumerable zurückgeben. Die ganze Magie geschieht innerhalb der AsyncHelper.Invoke-Methode. Dies ist es, was unsere Coroutine (die sich als gehackter Iterator tarnt) zum Laufen bringt. Es ist besonders darauf zu achten, dass der Iterator immer im aktuellen Synchronisationskontext ausgeführt wird, falls einer vorhanden ist. Dies ist wichtig, wenn versucht werden soll, wie await auf einem UI-Thread funktioniert. Dazu wird der erste MoveNext synchron ausgeführt und anschließend mit SynchronizationContext.Send der Rest von einem Arbeitsthread ausgeführt, der auch zum asynchronen Warten auf die einzelnen Schritte verwendet wird.

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;
    }
}

Das Ganze über den TaskCompletionSource war mein Versuch, die Art und Weise zu replizieren, wie await einen Wert "zurückgeben" kann. Das Problem ist, dass die Coroutine tatsächlich einen IEnumerable zurückgeben muss, da es sich nur um einen gehackten Iterator handelt. Also musste ich mir einen alternativen Mechanismus ausdenken, um einen Rückgabewert zu erfassen.

Dies hat einige offensichtliche Einschränkungen, aber ich hoffe, dies gibt Ihnen die allgemeine Idee. Es wird auch gezeigt, wie die CLR einen verallgemeinerten Mechanismus zum Implementieren von Coroutinen haben könnte, für die await und yield return allgegenwärtig verwendet werden, jedoch auf unterschiedliche Weise, um ihre jeweilige Semantik bereitzustellen.

Es gibt nur wenige Implementierungen und Beispiele für Coroutinen aus Iteratoren (Ausbeute).

Eines der Beispiele ist das Caliburn.Micro-Framework, das dieses Muster für asynchrone GUI-Operationen verwendet.Es kann jedoch leicht für allgemeinen asynchronen Code verallgemeinert werden.

Das MindTouch DReAM -Framework implementiert Coroutinen über dem Iterator-Muster, das Async / funktional sehr ähnlich ist.Warten Sie:

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

vs.

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

Result ist DReAMs Version von Task.Die Framework-DLLs funktionieren mit .NET 2.0+, aber um es zu erstellen, benötigen Sie 3.5, da wir heutzutage viel 3.5-Syntax verwenden.

Bill Wagner von Microsoft schrieb einen Artikel im MSDN Magazine darüber, wieSie können die Task Parallel Library in Visual Studio 2010 verwenden, um asynchrones Verhalten zu implementieren, ohne eine Abhängigkeit vom asynchronen ctp hinzuzufügen.

Task und Task<T> werden häufig verwendet, was auch den zusätzlichen Vorteil hat, dass Ihr Code nach dem Auslaufen von C # 5 gut vorbereitet ist, async und await zu verwenden.

Nach meiner Lektüre besteht der Hauptunterschied zwischen yield return und await darin, dass await explizit einen neuen Wert in die Fortsetzung zurückgeben kann.

SomeValue someValue = await GetMeSomeValue();

wohingegen Sie mit yield return dasselbe durch Bezugnahme erreichen müssten.

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top