×
Namespaces

Variants
Actions
Revision as of 02:32, 20 August 2013 by paulo.morgado (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Trazendo async/await ao serviço de contactos

From Nokia Developer Wiki
Jump to: navigation, search

Este artigo explica como contruir APIs para tirar partido das novas palavras chave async/await do C# 5.0 para programação assíncrona.

SignpostIcon XAML 40.png
WP Metro Icon WP8.png
SignpostIcon WP7 70px.png
Article Metadata

Testado com
SDK: built and tested against Windows Phone 7.5 SDK

Compatibilidade
Artigo
Tradução:
Por paulo.morgado
Última alteração feita por paulo.morgado em 20 Aug 2013

Contents

Introdução

Com as novas funcionalidades da linguagem de programação C#' relacionadas a programação assíncrona, é possível escrever forma sequencial trechos de código que serão executados de forma assíncrona.

No entanto, as novas funcionalidades assentam no modelo de assincronismo baseado em tarefas e nem todas as APIs o utilizam. Na verdade, para programação para o Windows Phone, de momento, não existem APIs assíncronas baseadas em tarefas.

Para as APIs utilizam o modelo de programação assíncrona (APM) (introduzido na primeira versão da plataforma .NET) existe uma conversão disponibilizada pelo método FromAsync da classe TaskFactory.

Mas não existe conversão para APIs que usam o modelo de programação assíncrono baseado em eventos (como é o caso do serviço de contactos do Windows Phone). A razão de não existir tal conversão é porque este não apresentam uma estrutura ou comportamento comum. Uns permitem cancelamento e erros (como é o caso da classe WebClient) e outros não (como é o caso da classe Contacts).

Neste artigo vamos aprender como converter uma API assíncrona baseada em eventos numa API assíncrona baseada em tarefas usando a classe Contacts como exemplo.

Visão Geral da Arquitetura

As seções abaixo usam o mesmo mecanismo para a conversão entre o padrão baseado em eventos e o padrão baseado em tarefas (embora usando diferentes mecanismos para estender a API).

O padrão de assincronismo baseado em tarefas permite que seja imediatamente devolvido um "valor futuro" ao chamador (representado pela classe Task). Este "valor real" estará disponível através da propriedade Result da Task quando a operação assíncrona terminar.

Esta tarefa (Task) a partir da qual obteremos o valor futuro é obitda criando uma instância da classe TaskCompletionSource do tipo pretendido. O valor da propriedade Task da TaskCompletionSource sera devolvido ao chamador que a aguardará. Enquando o chamador aguarda pela Task devolvida, ficará semanticamente bloqueado até que o resultado da tarefa esteja disponível através da chamada a SetResult() no tratamento do evento de conclusão da API baseada em eventos.

"Semanticamente bloqueado" quer dizer que o fluxo de execução do método chamador sera interrompida e retomada quando a operação assíncrona tiver sido completada. Isto normalmente quer dizer que o thread sera bloqueado. No entanto, em threads de UI (como é o caso da UI do Windows Phone) que necessitam que o sistema de mensagens (message pump) continue a trabalhar para manter a experiência de utilização com uma boa resposta, o fluxo de execução é retornado ao sistema de mensagens e quando a operação assíncrona terminar sera coiocada uma mensagem de continuação no sistema de mensagens e a execução é retomada na instrução seguinte.

Para mais informação, consultar How to: Use Components That Support the Event-based Asynchronous Pattern e Implement the Task-based Asynchronous Pattern.

Métodos de extensão

A forma mais simples de criar um resultado futuro é usando a classe TaskCompletionSource.

A forma de usar esta classe é criar uma instância TaskCompletionSource com um resultado do tipo pretendido e subscrever o evento pretendido para atribuir o resultado. Em seguida devolve-se ao chamador o valor futuro (representado pela classe Task):

public static class ContactsAsyncExtenstions
{
public static Task<IEnumerable<Contact>> SearchAsync(
this Contacts contacts,
string filter,
FilterKind filterKind)
{
var taskCompletionSource =
new TaskCompletionSource<IEnumerable<Contact>>();
 
EventHandler<ContactsSearchEventArgs> handler = null;
handler = (s, e) =>
{
contacts.SearchCompleted -= handler;
 
taskCompletionSource.TrySetResult(e.Results);
};
contacts.SearchCompleted += handler;
 
contacts.SearchAsync(filter, filterKind, null);
 
return taskCompletionSource.Task;
}
}

De notar que não temos controlo sobre a instância de Contacts sobre a qual estamos a trabalhar, por isso a primeira coisa que fazermos no tratamento do evento é cancelar a subscrição do evento. Como prática também devemos cancelar a subscrição do evento em caso de ocorrer algum erro.

Tip.pngTip: Se uma exceção pudesse ser lançada pela API de Contacts teríamos de chamar TaskCompletionSource.SetException com a exceção obtida no tratamento do evento. Este código não valida a ocorrência de exceções porque nenhuma está listada na documentação de Contacts.SearchAsync and ContactsSearchEventArgs.Result. Se Contacts.SearchAsync lançar uma exceção, esta será propaganda para o chamador uma vez que ainda está no caminho síncrono.

Tip.pngTip: Apesar de Contacts.SearchAsync não permitir cancelamento, um CancellationToken poderia ser usado para cancelar a operação assíncrona. Mas para APs que não permitem cancelamento, uma aproximação mais generalista como esta é uma melhor opção.

Usar o método de extensão SearchAsync seria algo como:

async Task<IEnumerable<Contact>> GetContactsAsync()
{
var contactsService = new Contacts();
var contacts = await contactsService.SearchAsync(filter: "filter", filterKind: FilterKind.None);
return contacts;
}

Usando async/await na implementação do método SearchAsync

Note-se na implementação anterior que, uma vez que não temos controlo sobre a instância de Contacts sobre a qual estamos a operar, a primeira coisa que fazemos no tratamento do evento é cancelar a subscrição do evento. De igual forma, caso ocorresse uma exceção na chamada ao método SearchAsync da classe Contacts deveríamos também cancelar a subscrição do evento.

Ao usarmos async e await também na implementação, o código torna-se mais simples de escrever e entender:

public async static Task<IEnumerable<Contact>> SearchAsync(this Contacts contacts, string filter, FilterKind filterKind)
{
var tcs = new TaskCompletionSource<IEnumerable<Contact>>();
 
EventHandler<ContactsSearchEventArgs> handler = (s, e) => { tcs.TrySetResult(e.Results); };
 
try
{
contacts.SearchCompleted += handler;
 
contacts.SearchAsync(filter, filterKind, null);
 
return await tcs.Task;
}
finally
{
contacts.SearchCompleted -= handler;
}
}

Extendendo a classe Contacts

Embora a utilização de métodos de extensão seja uma forma muito válida de estender tipos dos quais não podemos ou não queremos derivar, muitas vezes podem advir ganhos ao estender-se um tipo por delegação (que consiste em encapsular o tipo estendido). Ao estender a classe Contacts passamos a controlar o seu ciclo de vida e o acesso aos seus membros.

Na nossa implementação começamos por, ao invés de usar uma instância de Contacts fornecida pelo chamador, usaremos uma instância interna criada quando é criada uma instância da nossa classe:

public class ContactsAsync
{
private Contacts contacts = new Contacts();
}

Porque temos total controlo sobre a instância de Contacts, podemos ter apenas um tratamento global para o evento SearchCompleted e criar a subscrição logo após a criação da instância:

    public ContactsAsync()
{
this.contacts.SearchCompleted += (s, e) =>
{
// ...
};
}

Nas implementações anteriores tínhamos uma subscrição do evento para cada chamada e confiavamos no compilador de C# para capturar a instância de TaskCompletionSource e disponibilizá-la ao tratamento do evento. Agora que temos um trantamento global do evenot, como é que vamos disponibilizar ao tratamento do evento a instância de TaskCompletionSource correspondente a cada chamada?

Se repararem, as APIs baseadas em eventos geralmente têm uma versão do método que permite fornecer-lhe um estado que será acessível no tratamento do evento. No caso da classe Contacts isto é feito atrav+es do argumento state do método SearchAsync e da propriedade State da classe ContactsSearchEventArgs.

Sendo assim, a implementação do nosso método SearchAsync passa a ser:

public Task<IEnumerable<Contact>> SearchAsync(
string filter,
FilterKind filterKind)
{
var tcs = new TaskCompletionSource<IEnumerable<Contact>>();
 
this.contacts.SearchAsync(filter: filter, filterKind: filterKind, state: tcs);
 
return tcs.Task;
}

e o tratamento do evento passa a ser:

        (e.State as TaskCompletionSource<IEnumerable<Contact>>).TrySetResult(e.Results);

Para completar a implementação da nossa classe ContactsAsync falta expor a propriedade Accounts da classe Contacts através da nossa própria propriedade Accounts:

    public IEnumerable<Account> Accounts
{
get { return this.contacts.Accounts; }
}

Aqui fica a implementação completa da classe ContactsAsync:

public class ContactsAsync
{
private Contacts contacts = new Contacts();
 
public ContactsAsync()
{
this.contacts.SearchCompleted += (s, e) =>
{
(e.State as TaskCompletionSource<IEnumerable<Contact>>).TrySetResult(e.Results);
};
}
 
public IEnumerable<Account> Accounts
{
get { return this.contacts.Accounts; }
}
 
public Task<IEnumerable<Contact>> SearchAsync(
string filter,
FilterKind filterKind)
{
var tcs = new TaskCompletionSource<IEnumerable<Contact>>();
 
this.contacts.SearchAsync(filter: filter, filterKind: filterKind, state: tcs);
 
return tcs.Task;
}
}

Recursos

This page was last modified on 20 August 2013, at 02:32.
64 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×