terça-feira, 3 de junho de 2008

Parallel Extensions June 2008 CTP

Está disponível para download a CTP 2008 da Parallel Extensions. Há já algum tempo que venho a utilizar a versão anterior de Dez07 (inclusive em algum código de produção) sem grandes problemas. Para quem não conhece, aconselho vivamente a experimentar. Esta API não nos trás nada que não fosse possível fazer antes, trás sim uma simplicidade fantástica em tirar partido de hardware multi-core.

Penso que estamos à beira de uma grande revolução no modo de pensar e estruturar algoritmos. Tal como educámos o nosso modo de pensar de um modo sequencial e estruturado para um modo orientado a objectos, é tempo de educarmos o nosso pensamento para muti-processo. Quando desenvolvemos um algoritmo temos de pensar logo à partida nas dependências e nos blocos que são ou não dependentes e que podem ou não ser executados de modo assíncrono. Tal como disse atrás não é nada de novo mas a implementação era mais complexa e na maior parte dos casos não havia necessidade de complicar. A democratização do hardware multi-processador / multi-core e a simplicidade que nos trás esta API torna esta tecnologia acessível a um leque muito maior de situações do dia-a-dia.

sábado, 17 de maio de 2008

Como iniciar uma thread com múltiplos parâmetros

Ainda hoje surgiu esta questão. Necessito de executar um pedaço de código numa Thread separada mas tenho de lhe passar um conjunto de parâmetros. A solução clássica passa por criar uma classe que expõe as propriedades necessárias e executar a thread numa instância dessa classe. Algo do género:

MyThreadClass myThreadClass1 = new MyThreadClass() { P1 = 1, P2 = "teste" };
ThreadStart threadStart1 = new ThreadStart(myThreadClass1.start);
Thread thread1 = new Thread(threadStart1);
thread1.Start();
class MyThreadClass
{
public int P1 { get; set; }
public string P2 { get; set; }
public void start()
{
//algum código...
}
}

Pode-se também criar uma struct ou classe simples onde se colocam os parâmetros necessários e utilizar o ParameterizedThreadStart, mas a questão mantêm-se: se o código a executar paralelamente é algo muito simples, não deveria haver uma solução mais simples? Claro que há. O construtor da classe Thread pode receber uma ThreadStart ou uma ParameterizedThreadStart. Ambos são delegates. Porque não construir ThreadStart que aponte para um simples delegate que por sua vez chama o método que se pretende paralelo? Como por exemplo:

ThreadStart threadStart1 = delegate { threadWith2Parameters("texto 1", "texto 2"); };
Thread thread1 = new Thread(threadStart1);
thread1.Start();
private void threadWith2Parameters(string i1, string i2)
{
//algum código...
}

Assim é possível executar qualquer método que esteja incluído na mesma classe independentemente dos seus parâmetros. Compactando mais o código fica apenas:

(new Thread((ThreadStart)delegate { threadWith2Parameters("texto 1", "texto 2"); })).Start();

Em ambientes WinForms é quase sempre preferível executar tarefas possivelmente demoradas em Thread's paralelas para evitar o congelamento da janela. Com este tipo de mecanismos evita-se que o código fique demasiado complexo. Imagine-se por exemplo um botão que executa um qualquer serviço remoto:

private void button1_Click(object sender, EventArgs e)
{
(new Thread((ThreadStart)delegate { listClients("sousa", "lisboa"); })).Start();
}
private void listClients(string name, string address)
{
this.BeginInvoke((MethodInvoker)delegate()
{
//método para bloquear controlos...
});
try
{
//algum código...
}
catch (Exception ex)
{
//tratar o erro...
}
finally
{
this.BeginInvoke((MethodInvoker)delegate()
{
//método para desbloquear controlos...
});
}
}

Existem por ai outras abordagens. Fica apenas mais uma.

sexta-feira, 16 de maio de 2008

Desenvolver software ou construir casas?

Diversas vezes que somos confrontados com esta questão:

    Porque razão desenvolver software não é um processo previsível e controlado como outras áreas de engenharia, como construção civil, por exemplo?

A resposta comum a esta questão passa normalmente por tentativas de mecanizar, burocratizar, medir, controlar o processo de desenvolvimento. Passa por adoptar metodologias monstruosas que acrescentam ao desenvolvimento uma quantidade de processos muitas vezes supérfluos e sem qualquer valor acrescentado. Na maior parte dos casos inclusive acabamos por nos esquecer que o principal resultado, aquele pelo qual o cliente paga que é:… código a funcionar. O principal produto num projecto de desenvolvimento de um portal, por exemplo, não é uma lista de casos de uso nem diagramas de sequência nem sequer gráficos de GANT! O principal produto é o portal a FUNCIONAR!

Ora o cenário comum é inundar as equipas de desenvolvimento com processos de qualidade burocráticos, numa tentativa de "controlar" e "tornar previsível" o processo de desenvolvimento. Em diversos casos que assisti até hoje, normalmente o resultado é… nenhum. O desenvolvimento é feito da mesma forma e no final do dia ou da semana (ou mesmo do projecto), os programadores vão consumir tempo e dinheiro a escrever documentação que não vai ter qualquer utilização que não seja mostrar nas auditorias.

Não quero com isto dizer que a construção de uma aplicação deva ser feita sem qualquer controlo, mas sim que a documentação a criar deve ser a necessária para ajudar no desenvolvimento do produto. Consumir tempo a criar diagramas de sequencia depois do software estar desenvolvido é apenas perda de tempo. Ou é feito antes do desenvolvimento no intuito de se perceber o que se vai fazer e encontrar a melhor forma de o fazer ou é simplesmente perda de tempo.

Analisando o outro lado (que conheço muito pouco), quando se desenvolve um projecto de construção, os arquitectos e engenheiros têm um determinado conhecimento sobre os materiais que vão ser utilizados, o seu comportamento, a sua utilização, a sua assemblagem. Agora pergunto: qual é o domínio de um arquitecto de software (quando existe um) ou um chefe de projecto sobre as linguagens, plataformas, componentes ou protocolos que vão ser utilizados? Já para não falar que nas engenharias tradicionais, tanto as matérias primas como produto final é quase sempre algo materializável, palpável. O desenvolvimento de software é um processo criado na totalidade sobre abstracção. Posso ter uma aplicação que tenha algumas funcionalidades, mas não deixa de ser uma abstracção, bem diferente de uma parede, uma janela ou uma casa. Quer queiramos ou não, o desenvolvimento de software é sempre muito mais complexo e dificilmente quem desenha ou planeia tem domínio suficiente sobre a sua construção para que consiga prever o resultado na sua totalidade. O arquitecto pode não saber como se coloca um parapeito numa janela, mas existe um conhecimento adquirido de que isso é possível e além do mais, os materiais são físicos e pouco mutáveis. Em último caso com mais umas pancadas e a coisa vai ao sítio. Do outro lado quando se planeia uma comunicação entre dois sistemas não existe domínio concreto sobre os problemas que vão existir em garantir a comunicação. Vão aparecer problemas de protocolos, autenticações, localização, etc… que podem ser resolvidos em muito pouco tempo ou podem gerar perdas de dias ou semanas. Tal como o arquitecto coloca uma janela algures numa parede, para quem planeia uma aplicação é simples desenhar num diagrama uma seta que significa uma comunicação entre dois sistemas, mas o conhecimento que os ambos têm sobre a implementação concreta, a complexidade ou os problemas que irão surgir a meio não é de todo comparável.

Então em que ficamos? Simplesmente são processos demasiado distintos para que devam ser comparados. Quando me colocam esta questão contraponho:

"Então as obras de engenharia civil nunca falham, nunca excedem orçamentos e estão sempre terminadas na data estipulada?"

quarta-feira, 16 de abril de 2008

Cross-Thread com Anonymous Methods

Quando desenvolvemos aplicações clientes é comum criar threads para executar tarefas sem que o Form fique bloqueado, sem responder ao utilizador nem actualizar a sua aparência. Até aqui tudo bem, mas imaginemos que queremos por exemplo alimentar uma lista com mensagens ou alterar propriedades de alguns controlos. Se o tentarmos fazer a partir da mesma thread onde está a correr o nosso processo, obtemos erros do tipo System.InvalidOperationException com a descrição "Cross-thread operation not valid: Control 'TextBox1' accessed from a thread other than the thread it was created on."

O que se passa é que um componente que está no ecrã apenas pode ser modificado pela thread que gere o próprio ecrã. Como resolvemos isto? O método típico é com recurso a um delegate, assim:

delegate void UpdateLabelDelegate(Label label1, string text);
private void updateLabel(Label label1, string text)
{
if (label1.InvokeRequired)
{
label1.BeginInvoke(new UpdateLabelDelegate(updateLabel), label1, text);
return;
}
label1.Text = text;
}
void MyThreadMethod()
{
//more code...

updateLabel(label1, "New text");
//more code...
}

O processo até é simples: verificamos se a thread actual pode modificar o componente (label1.InvokeRequired) e se não puder ser solicitamos à thread que suporta o componente que execute o nosso código, utilizando o delegate que lhe providenciamos. Não é complicado, mas não é muito agradável ter todo este código para simplesmente alterar o texto de uma Label ou uma qualquer propriedade de um outro componente no Form.

Desde a versão 2.0 da Framework .Net que temos a possibilidade de criar Anonymous Methods. Com este recurso o código pode ficar muito mais simples:

void MyThreadMethod()
{
//more code...
label1.BeginInvoke((MethodInvoker)delegate() { label1.Text = "New text"; });
//more code...
}

Ou seja, podemos actualizar os componentes directamente da thread onde estamos a trabalhar, com uma única linha de código. Bem mais interessante, não?