Breno Ferreira

Opniões e códigos de um desenvolvedor de software .NET.

Posts Tagged ‘C#

Async no C# 5–Advanced

leave a comment »

Olá pessoal, tudo bem?

Há algum tempo atrás, eu postei aqui no blog sobre assincronia no C# 5. Hoje irei continuar falando sobre o assunto, porém, irei abordar alguns conceitos um pouco mais avançados para que voce consiga entender melhor como as coisas funcionam e tirar melhor proveito dessa nova funcionalidade da linguagem.

Under the Covers

Acho que sempre é bom saber o que acontece por debaixo dos panos, então, quando abri o código que o compilador do C# gera quando utilizamos o await, vi o seguinte trecho de código:

Esse código:

var webClient = new WebClient();
var html = await webClient.DownloadStringTaskAsync("http://www.bing.com");
return html;

Vira este código:

private struct <DownloadBingHomePageHTMLAsync>d__3 : <>t__IStateMachine
{
	private int <>1__state;
	public AsyncTaskMethodBuilder<string> <>t__builder;
	public Action <>t__MoveNextDelegate;
	public WebClient <webClient>5__4;
	public string <html>5__5;
	private object <>t__stack;
	private object <>t__awaiter;
	public void MoveNext()
	{
		string result;
		try
		{
			int num = this.<>1__state;
			TaskAwaiter<string> taskAwaiter;
			if (num != 1) //if execution of async method is not finished (State == 1 means that the execution is running)
			{
				if (this.<>1__state == -1)
				{
					return;
				}
				this.<webClient>5__4 = new WebClient();
				taskAwaiter = this.<webClient>5__4.DownloadStringTaskAsync("http://www.bing.com").GetAwaiter(); //start the async operation, and get the TaskAwaiter
				if (!taskAwaiter.IsCompleted) //if the async operation is not completed
				{
					this.<>1__state = 1; //set the state to "Running"
					TaskAwaiter<string>[] array;
					(array = new TaskAwaiter<string>[1])[0] = taskAwaiter;
					this.<>t__awaiter = array;
					Action action;
					if ((action = this.<>t__MoveNextDelegate) == null)
					{
						Task<string> arg_9A_0 = this.<>t__builder.Task;
						action = new Action(this.MoveNext); //define a delegate to the method itself.
						((<>t__IStateMachine)action.Target).<>t__SetMoveNextDelegate(action);
					}
					array[0].OnCompleted(action); //use that delegate (which points to the method itself) as a method which will be called when the async operation completes
					return;
				}
			}
			else //if the async is complete
			{
				taskAwaiter = ((TaskAwaiter<string>[])this.<>t__awaiter)[0]; //get the TaskAwaiter
				this.<>t__awaiter = null;
				this.<>1__state = 0; //set the state to "Initial"
			}
			string arg_110_0 = taskAwaiter.GetResult(); //get the result of the async operation from the TaskAwaiter
			taskAwaiter = default(TaskAwaiter<string>);
			string text = arg_110_0; //continue with the execution of the code that was defined after the "await" keyword (in this case, return the html as a String)
			this.<html>5__5 = text;
			result = this.<html>5__5;
		}
		catch (Exception exception)
		{
			this.<>1__state = -1;
			this.<>t__builder.SetException(exception);
			return;
		}
		this.<>1__state = -1; //set the result to "Complete"
		this.<>t__builder.SetResult(result);
	}
	[DebuggerHidden]
	void <>t__IStateMachine.<>t__SetMoveNextDelegate(Action param0)
	{
		this.<>t__MoveNextDelegate = param0;
	}
}

É um trecho de código bem extenso, como voce pode ver. Adicionei alguns comentários para que voce possa entender melhor, mas irei explicar melhor o que acontece.
Para cada await que adicionamos ao nosso código, o compilador cria uma estrutura de máquina de estados, responsável por coordenar a operação assíncrona e dar continuidade a execução do código.

Dentro da estrutura, há um método chamado MoveNext que possui toda a lógica. Basicamente ele é chamado quando há uma keyword await. Quando este método é chamado, ele verifica se a operação assíncrona já foi iniciada. Como ainda não foi, ele inicia a execução assíncrona, e então seta o estado para “Running“. Então ele guarda uma referencia para um Awaiter que será usado para definir um callback que será chamado quando a operação assíncrona terminar. Esse callback é definido para a própria função (MoveNext). Quando a operação termina, o método MoveNext() é novamente chamado, mas dessa vez ele verifica que o estado é “Running”, então com a referencia para o Awaiter ele pega o resultado retornado pela operação assíncrona e continua com a execução do código.

Tasks & Threads

Outro conceito bem importante de saber ao usar Tasks e Async é que essas Tasks não adicionam Threads adicionais. Ou seja, voce não precisa se preocupar com acesso concorrente, locks entre outros problemas inerentes a programação paralela. É claro que a operação assíncrona poderá ser executada em uma Thread separada (caso contrário é quase impossível executar um código de maneira assíncrona), mas nenhuma Thread extra será criada. O async e await são simplesmente syntax-sugars para callbacks. Podemos ver isso no código a seguir:

private Task<String> DownloadStringAsync(string uri)
{
    var webclient = new WebClient();

    var downloadTaskCompletionSource = new TaskCompletionSource<String>();
    var downloadTask = downloadTaskCompletionSource.Task;

    webclient.DownloadStringCompleted += (sender, e) =>
        {
            downloadTaskCompletionSource.SetResult(e.Result);
        };

    webclient.DownloadStringAsync( new Uri(uri));
    return downloadTask;
}

string str = await DownloadStringAsync("http://www.google.com");

No código acima, utilizamos a classe TaskCompletionSource, que pode ser usada para devolver uma Task associada à uma operação assíncrona. Neste caso, a Task será usada simplesmente para representar a operação assíncrona e dar ao usuário o poder de setar Continuations. Não há nenhuma Thread adicional envolvida.

UI, Libs & Synchronization Contexts

Quando escrevemos código assíncrono antigamente, as vezes tínhamos que nos preocupar com execução de código em Threads apropriadas. Por exemplo: quando executamos método assíncrono na camada de apresentação, por exemplo em Silverlight, WPF, etc., quando o callback do método assíncrono era chamado para tratarmos o resultado, precisávamos trocar o contexto das Threads caso fosse necessário fazer alguma atualização na interface. Para fazermos isso, era utilizado um SynchronizationContext (em Silverlight ou WPF era mais conhecido pela propriedade Dispatcher) que era responsável por enfileirar um delegate para execução na UI Thread (Thread responsável por renderizar os elementos de interface e tratar qualquer interação com o usuário).

Por padrão, quando utilizamos a keyword await, esse processo de troca de contexto de Threads é feito de maneira automática, ou seja, quando a operação assíncrona terminar, ele automaticamente utiliza o SynchroniationContext padrão para voltar para a Thread ideal. Isso é muito bom quando estamos desenvolvendo nossas aplicações que possuem algum tipo de UI (User Interface), como na maioria dos casos. Mas se estivermos desenvolvendo alguma biblioteca reutilizável, esse comportamento pode não ser o mais apropriado. Primeiro que essa troca de contexto tem um custo de performance que pode não ser apropriado, segundo que seu uso pode vir a ter alguns efeitos colaterais indesejados. Imagine o seguinte:

async Task AsyncOp()
{
	Task.Delay(1000); //simulate asynchronous operation
}

var task = AsyncOp();
task.Wait();

Esse código irá causar um deadlock. Porque? Acontece que quando chamamos o método Wait() de uma Task, ele bloqueia a Thread até a operação da Task terminar. Mas quando a operação assíncrona terminar e ele passar pelo await, ele vai tentar voltar para a UI Thread, que vai estar bloqueada, então temos um deadlock.
Para isso não acontecer, basta chamarmos o método ConfigureAwait(Boolean continueOnCapturedContext) da Task, e passarmos o valor false. Assim, ele irá continuar a execução da operação assíncrona na mesma Thread, sem troca de contexto.

async Task AsyncOp()
{
	Task.Delay(1000).ConfigureAwait(false); //simulate asynchronous operation
}

var task = AsyncOp();
task.Wait();

Bem, era isso pessoal. Até a próxima 😉

Advertisements

Written by Breno Ferreira

03/01/2012 at 20:48

Posted in C#

Tagged with ,

Async no C#5

with one comment

Olá pessoal,

Hoje irei falar sobre um assunto bastante comentado nos últimos meses: assincronia. Assincronia está longe de ser um conceito novo, estando presente em várias linguagens a bastante tempo. No caso de linguagens C#, é possível escrever código assíncrono desde a primeira versão da linguagem.

Porque escrever código assíncrono

Operações assíncronas no nosso código são importantes quando desejamos executar alguma operação em background. Isso é de extrema importância quando precisamos ter uma interface gráfica responsiva, ou seja, que não fica bloqueada esperando alguma operação terminar. Quem nunca viu uma aplicação travar conforme a imagem abaixo?

image

Isso acontece por que a Thread responsável por tratar os comandos do usuário (geralmente chamada de UI Thread), está ocupada esperando alguma operação completar (no caso do Notepad acima, ela está bloqueada esperando um arquivo terminar de ser lido). Por isso, ela não pode executar nenhum comando do usuário. Para que isso não ocorra, devemos executar essas operações que levam um certo tempo para completar de maneira assíncrona, ou seja, em background, assim sendo possível deixar outras operações sejam executadas ao mesmo tempo.

Código Assíncrono no C#

Antes da versão 5 da linguagem, escrever código assíncrono era um tanto tedioso, e propício a erros. O desenvolvedor tinha que lidar com a interface IAsyncResult e então ter que lidar com callbacks. Abaixo está um exemplos de como isso era feito:

private void DownloadAsync(String uri)
{
	try
	{
		var webRequest = WebRequest.Create(uri);
		webRequest.BeginGetResponse(new AsyncCallback(HandleResponse), null);
	}
	catch(Exception)
	{
		Console.WriteLine("Error while downloading async");
	}
}

private void HandleResponse(IAsyncResult result)
{
	try
	{
		var response = webRequest.EndGetResponse(responseResult);
		StreamReader reader = new StreamReader(response.GetResponseStream());
		Console.WriteLine(reader.ReadToEnd());
	}
	catch(Exception)
	{
		Console.WriteLine("Error while handling download response");
	}
}

Repare que o código baseado em callbacks pode ficar meio confuso. O exemplo acima é bem simples, mas em casos mais complexos, com muitas chamadas assíncronas, o código tende a ficar bem complicado de ler, pois há “desvios” no código, e o programador é obrigado a desviar a leitura para outro trecho do código para ver a continuação da operação assíncrona. Se começarmos a adicionar IFs e Loops no código assíncrono, e ainda ter que fazer o tratamento de Exceptions, o código irá ficar bem complicado.

Async e Await

Com base nessas ocasionais dificuldades de se escrever código assíncrono, o time de desenvolvimento do C# criou uma nova técnica de se escrever código assíncrono no C#. Essa nova maneira de escrever código assíncrono no C# faz uso de duas novas keywords: async e await.

private async Task<String> DownloadAsync(String uri)
{
	try
	{
		var webRequest = WebRequest.Create(uri);
		var response = await webRequest.GetResponseAsync();

		StreamReader reader = new StreamReader(response.GetResponseStream());
		return reader.ReadToEnd();
	}
	catch(Exception)
	{
		Console.WriteLine("Error while downloading async");
	}
}

Bem mais simples, certo? O código ficou bem mais simples de entender, e ao mesmo tempo, está praticamente idêntico ao código síncrono, ou seja, sem callbacks e IAsyncResults espalhados pelo código.

Para dizermos que um método possui algum tipo de operação assíncrona, precisamos marcar o método como assíncrono, utilizando a keyword async antes do tipo de retorno do método. E quando desejamos receber o resultado de uma operação assíncrona, utilizamos a keyword await.

Importante: repare que a keyword await não faz com que a Thread bloqueie e literalmente espere o resultado da operação terminar. O que de fato acontece é que ao chegar no ponto onde há um await, o método retorna, e o que estiver abaixo (no exemplo acima, a instanciação da StreamReader e o retorno do resultado), é executado quando a operação assíncrona terminar, como se houvesse um callback mascarado.

Repare que ao invés de retornarmos uma String, retornamos uma Task<String>. Isso se deve ao fato de que o async do C# faz uso de Tasks para coordenar o fluxo de execução do código. Tasks nada mais são do que uma implementação de um conceito conhecido como Futures. Pense como se fosse uma representação de uma operação que ainda não completou, e que seu resultado ainda é desconhecido. Quando esta operação completar, quem tiver uma referência a esta Future (Task) irá ser notificado de que a operação terminou e poderá obter o resultado da operação. Então, no método acima, quando a execução chega em uma instrução que contém a keyword await, o método retorna uma Task representando a operação assíncrona que está sendo executada, neste caso, o download. Quando a operação terminar, quem estava em estado de await irá ser notificado e irá poder continuar com a execução do código.

Mais alguns exemplos

Operação assíncrona em um Loop:

public async Task<int> CountToTen()
{
	int count = 1;
	for (int i = 0; i < 10; i++)
	{
		await Task.Delay(500); //simulating async work
		count += i;
	}

	return count;
}

Neste exemplo estamos em um loop e executamos uma operação assíncrona no escopo deste loop. Como estamos usando a keyword await, a próxima iteração do loop só irá executar quando a operação assíncrona terminar.

Outra maneira de se fazer o await:

private static void PrintFromOneToThenThenFromElevenToTwentyUsingTasks()
{
	var task = 
		new Task(() =>
				Enumerable.Range(1, 10).ToList().ForEach(i =>
				{
					Thread.Sleep(10);
					Console.WriteLine(i);
				})
			 );

	task
		.GetAwaiter()
		.OnCompleted(() =>
					Enumerable.Range(11, 10).ToList().ForEach(i =>
					{
						Thread.Sleep(10);
						Console.WriteLine(i);
					})
				  );
	task.Start();

	task.Wait();
}

Neste exemplo, fazemos o await manualmente, ou seja, fazemos (grosseiramente) o que o compilador faz quando encontra a keyword await. O que acontece é que aqui pegamos um Awaiter e definimos um callback (action passada como parâmetro para o método OnCompleted) que será chamado quando a operação assíncrona terminar e pegamos o resultado (GetResult).

Conclusão

Vimos o quão fácil ficou escrever código assíncrono no C#5, ficando bastante parecido com código síncrono. Assim, conseguimos uma maior facilidade na hora de lidar com as complexidades que ocorrem quando a complexidade do nosso código aumenta.

Para saber mais, acesse a página do Async CTP na MSDN. Lá voce poderá baixar o Async CTP para o Visual Studio 2010, ou então baixar a versão CTP do Visual Studio 11 (recomendado!), baixar um Whitepaper, e assistir alguns vídeos do time do C# explicando em maiores detalhes. Publiquei também dois Gists (aqui e aqui) com alguns exemplos descritos aqui no post e mais alguns. Para mais exemplos, também recomendo acessar o 101 Async Examples

Abraços

Breno

PS: Gostaria de agradecer ao meu amigo Rodrigo Vidal por ter me ajudado e ter dado umas dicas para este post!

Written by Breno Ferreira

06/11/2011 at 20:46

Posted in C#

Tagged with , , ,