2011-03-23 11 views
6

¿Cómo implementaría algo que funciona de manera similar a la palabra clave Async CTP await? ¿Existe una implementación simple que funcione como await en todos los casos, o await requiere implementaciones diferentes para diferentes escenarios?Cómo implementar aguardar sin Async CTP

+2

Acabo de publicar un artículo sobre cómo [Esperar tareas en C# 4 usando Iteradores] (http://www.codeproject.com/Articles/504197/Await-Tasks-in-Csharp4-using-Iterators). Sorprendentemente, no es "hacky" ni muy feo, y AFAICT funciona igual de esperándolo. ¡Echale un vistazo! –

Respuesta

8

await siempre implica el mismo tipo de transformación, pero es muy doloroso. La biblioteca lado de await no es demasiado complicada, pero lo difícil es que el compilador crea una máquina de estado para usted, permitiendo que la continuación vuelva al lugar correcto.

Es posible que mi uso hacky de bloques iteradores (retorno de rendimiento) podría simular algo similar ... pero sería bastante feo.

Di un DevExpress webinar on what the compiler is doing behind the scenes a few weeks ago - que muestra el código decompilado de un par de ejemplos, así como también explica cómo el compilador crea una tarea para devolver, y lo que el "awaiter" tiene que hacer. Puede serle útil.

+0

Puede encontrar un seminario web que Jon mencionó en youtube aquí: http://www.youtube.com/watch?v=HpA2x_JvLD4 – ShitalShah

2

Hay algunas implementaciones y ejemplos de corutinas hechas de iteradores (rendimiento).

Uno de los ejemplos es Caliburn.Micro framework, que usa este patrón para operaciones de GUI asíncronas. Pero se puede generalizar fácilmente para el código asíncrono general.

2

El marco MindTouch DReAM implementa corrutinas en la parte superior del iterador que es funcionalmente muy similar a asíncrono/esperan:

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

vs

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

Result es la versión del sueño de Task. Los marcos dlls funcionan con .NET 2.0+, pero para construirlo necesita 3.5, ya que estamos usando mucha sintaxis 3.5 en estos días.

9

La nueva palabra clave await tiene una semántica similar a la palabra clave yield return existente, ya que ambos hacen que el compilador genere la máquina de estado de estilo de continuación para usted. Entonces es posible hackear algo usando iteradores que tienen algunos de los mismos comportamientos que el CTP Async.

Esto es lo que se vería.

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

Desde yield return genera un IEnumerable nuestra co-rutina debe devolver un IEnumerable. Toda la magia ocurre dentro del método AsyncHelper.Invoke. Esto es lo que hace que funcione nuestra coroutine (enmascarada como un iterador pirateado). Se debe tener especial cuidado para asegurarse de que el iterador siempre se ejecute en el contexto de sincronización actual, si existe, lo cual es importante cuando se trata de simular cómo funciona await en un subproceso de interfaz de usuario. Hace esto ejecutando el primer MoveNext de forma síncrona y luego usando SynchronizationContext.Send para hacer el resto de un subproceso de trabajo que también se usa para esperar asincrónicamente en los pasos individuales.

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

El conjunto poco sobre el TaskCompletionSource fue mi intento de replicar la forma await puede "retorno" un valor.El problema es que la coroutine tiene para realmente devolver un IEnumerable ya que no es más que un iterador pirateado. Así que necesitaba encontrar un mecanismo alternativo para capturar un valor de retorno.

Hay algunas limitaciones flagrantes con esto, pero espero que esto le dé la idea general. También demuestra cómo el CLR podría tener tener un mecanismo generalizado para implementar corutinas para las cuales await y yield return usarían de forma ubicua, pero de diferentes maneras para proporcionar su semántica respectiva.

0

De mi lectura, las principales diferencias entre yield return y await pueden indicar que se devuelve explícitamente un nuevo valor en la continuación.

SomeValue someValue = await GetMeSomeValue(); 

mientras que con yield return, que tendría que lograr lo mismo por referencia.

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

Bill Wagner de Microsoft escribió an article in MSDN Magazine acerca de cómo se puede utilizar la biblioteca de tareas en paralelo en Visual Studio 2010 para aplicar asíncrono como el comportamiento sin añadir una dependencia en el CTP asíncrono.

Utiliza Task y Task<T> ampliamente que también tiene la ventaja añadida de que una vez C# 5 está fuera, su código estará bien preparado para empezar a utilizar async y await.

Cuestiones relacionadas