Domanda

Come implementereste qualcosa che funzioni in modo simile alla parola chiave await CTP asincrono?Esiste un'implementazione semplice che funzioni come await in tutti i casi o await richiede implementazioni diverse per scenari diversi?

È stato utile?

Soluzione

await implica sempre lo stesso tipo di trasformazione, ma è piuttosto doloroso.Il lato libreria di await non è troppo complicato, ma la parte complicata è che il compilatore costruisce una macchina a stati per te, consentendo alla continuazione di tornare al posto giusto.

È possibile che il mio uso hacky dei blocchi iteratori (rendimento del rendimento) possa simulare qualcosa di simile ... ma sarebbe piuttosto brutto.

Ho tenuto un webinar DevExpress su ciò che fa il compilatore dietro le quinte qualche settimana fa -che mostra il codice decompilato da un paio di esempi, oltre a spiegare come il compilatore costruisce un'attività da restituire e cosa deve fare il "waititer".Potrebbe esserti utile.

Altri suggerimenti

La nuova parola chiave await ha una semantica simile alla parola chiave yield return esistente in quanto entrambe fanno sì che il compilatore generi la macchina a stati dello stile di continuazione per te. Quindi è possibile hackerare qualcosa insieme usando iteratori che hanno alcuni degli stessi comportamenti del CTP Async.

Ecco come sarebbe.

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

Poiché yield return genera un IEnumerable, la nostra coroutine deve restituire un IEnumerable. Tutta la magia avviene all'interno del metodo AsyncHelper.Invoke. Questo è ciò che fa funzionare la nostra coroutine (mascherata da iteratore hackerato). Ci vuole particolare cura per assicurarsi che l'iteratore sia sempre eseguito nel contesto di sincronizzazione corrente, se ne esiste uno, il che è importante quando si cerca di simulare il funzionamento di await su un thread dell'interfaccia utente. Lo fa eseguendo il primo MoveNext in modo sincrono e quindi utilizzando SynchronizationContext.Send per fare il resto da un thread di lavoro che viene anche utilizzato per attendere in modo asincrono i singoli passaggi.

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

L'intera parte sul TaskCompletionSource era il mio tentativo di replicare il modo in cui await può "restituire" un valore. Il problema è che la coroutine deve restituire effettivamente un IEnumerable poiché non è altro che un iteratore hackerato. Quindi avevo bisogno di trovare un meccanismo alternativo per acquisire un valore di ritorno.

Ci sono alcune limitazioni evidenti in questo, ma spero che questo ti dia l'idea generale. Dimostra anche come il CLR potrebbe avere un meccanismo generalizzato per l'implementazione di coroutine per le quali await e yield return utilizzerebbero ubiquitariamente, ma in modi diversi per fornire le rispettive semantiche.

Ci sono poche implementazioni ed esempi di coroutine fatte di iteratori (yield).

Uno degli esempi è il framework Caliburn.Micro, che utilizza questo schema per le operazioni GUI asincrone.Ma può essere facilmente generalizzato per il codice asincrono generale.

Il framework MindTouch DReAM implementa Coroutines in cima al pattern Iterator che è funzionalmente molto simile ad Async /Attendi:

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

contro

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

Result è la versione DReAM di Task.Le DLL del framework funzionano con .NET 2.0+, ma per crearle è necessario 3.5, poiché oggigiorno utilizziamo molta sintassi 3.5.

Bill Wagner di Microsoft ha scritto un articolo su MSDN Magazine su comeè possibile utilizzare la libreria Task Parallel in Visual Studio 2010 per implementare un comportamento di tipo asincrono senza aggiungere una dipendenza dal ctp asincrono.

Utilizza Task e Task<T> ampiamente, il che ha anche il vantaggio aggiuntivo che una volta uscito C # 5, il tuo codice sarà ben preparato per iniziare a utilizzare async e await.

Dalla mia lettura, le principali differenze tra yield return e await è che await può fornire esplicitamente restituire un nuovo valore nella continuazione.

SomeValue someValue = await GetMeSomeValue();

mentre con yield return, dovresti ottenere la stessa cosa per riferimento.

var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top