Pergunta

Como você implementaria algo que funcione de forma semelhante à palavra-chave await Async CTP?Existe uma implementação simples que funciona como await em todos os casos, ou await requer diferentes implementações para diferentes cenários?

Foi útil?

Solução

await sempre envolve o mesmo tipo de transformação - mas é bem doloroso.O lado da biblioteca de await não é muito complicado, mas a parte complicada é que o compilador constrói uma máquina de estado para você, permitindo que a continuação volte ao lugar certo.

É possível que meu uso hacky de blocos de iterador (retorno de rendimento) você pudesse fingir algo semelhante ... mas seria muito feio.

Eu dei um webinar DevExpress sobre o que o compilador está fazendo nos bastidores algumas semanas atrás -que mostra o código descompilado de alguns exemplos, além de explicar como o compilador constrói uma tarefa para retornar e o que o "waititer" tem que fazer.Pode ser útil para você.

Outras dicas

A nova palavra-chave await tem semântica semelhante à palavra-chave yield return existente, pois ambas fazem com que o compilador gere a máquina de estado de estilo de continuação para você. Portanto, é possível hackear algo em conjunto usando iteradores que tenham alguns dos mesmos comportamentos do CTP Async.

Aqui está como seria.

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

Já que yield return gera um IEnumerable, nossa co-rotina deve retornar um IEnumerable. Toda a mágica acontece dentro do método AsyncHelper.Invoke. Isso é o que mantém nossa co-rotina (disfarçada de iterador hackeado). É necessário um cuidado especial para garantir que o iterador seja sempre executado no contexto de sincronização atual, se houver, o que é importante ao tentar simular como await funciona em um thread de IU. Ele faz isso executando o primeiro MoveNext de forma síncrona e, em seguida, usando SynchronizationContext.Send para fazer o resto a partir de uma thread de trabalho que também é usada para esperar de forma assíncrona as etapas individuais.

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

A parte toda sobre o TaskCompletionSource foi minha tentativa de replicar a maneira como await pode "retornar" um valor. O problema é que a co-rotina tem que retornar um IEnumerable, uma vez que nada mais é do que um iterador hackeado. Portanto, eu precisava criar um mecanismo alternativo para capturar um valor de retorno.

Existem algumas limitações gritantes nisso, mas espero que isso lhe dê uma ideia geral. Também demonstra como o CLR poderia ter um mecanismo generalizado para implementar corrotinas para as quais await e yield return usariam de forma ubíqua, mas de maneiras diferentes para fornecer suas respectivas semânticas.

Existem poucas implementações e exemplos de corrotinas feitas de iteradores (rendimento).

Um dos exemplos é o framework Caliburn.Micro, que usa esse padrão para operações de GUI assíncronas.Mas pode ser facilmente generalizado para código assíncrono geral.

A estrutura MindTouch DReAM implementa Coroutines no topo do padrão Iterator que é funcionalmente muito semelhante ao Async /Aguarde:

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

vs.

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

Result é a versão DReAM de Task.As dlls do framework funcionam com .NET 2.0+, mas para construí-las você precisa de 3.5, já que estamos usando muita sintaxe 3.5 atualmente.

Bill Wagner, da Microsoft, escreveu um artigo na MSDN Magazine sobre comovocê pode usar a Biblioteca Paralela de Tarefas no Visual Studio 2010 para implementar o comportamento assíncrono sem adicionar uma dependência no ctp assíncrono.

Ele usa Task e Task<T> extensivamente, o que também tem o benefício adicional de que, quando o C # 5 for lançado, seu código estará bem preparado para começar a usar async e await.

Pela minha leitura, as principais diferenças entre yield return e await é que await pode fornecer explicitamente retornar um novo valor para a continuação.

SomeValue someValue = await GetMeSomeValue();

enquanto com yield return, você teria que fazer a mesma coisa por referência.

var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top