Uma das formas que temos para consumir serviços WCF em um cliente qualquer, é utilizando a classe ChannelFactory<TChannel>, assim como eu já mostrei um exemplo neste post. Geralmente utilizamos esta técnica quando optamos por compartilhar os tipos (incluindo a interface que representa o contrato) entre o serviço e o cliente, evitando a publicação do documento WSDL, a reconstrução destes tipos do lado do cliente e, principalmente, a necessidade de efetuar a referência do serviço através da IDE do Visual Studio .NET.
Atenção: Antes de prosseguir a leitura deste post, aconselho que leia os seguintes artigos:
Quando fazemos a referência ao serviço através do Visual Studio, há uma opção chamada “Generate asynchronous operations”, que para cada operação encontrada no serviço, um par de métodos BeginNomeDaOperacao e EndNomeDaOperacao serão criados, e que irão trabalhar em conjunto, para que assim, o cliente consiga invocar a respectiva operação de forma assíncrona. O problema disso é que muitas vezes o contrato não oferece suporte à chamadas assíncronas, o que nos obrigará a criar toda a infraestrutura do lado do cliente para suportar isso. Imagine que temos o seguinte contrato:
[ServiceContract]
public interface IContrato
{
[OperationContract]
string FazAlgo(string value);
}
Como não temos a versão assíncrona do método FazAlgo, temos que recorrer à delegates para conseguir efetuar a chamada. Sendo assim, o código do lado do cliente, poderia ficar da seguinte forma:
private ChannelFactory<IContrato> _factory;
private IContrato _proxy;
public Form1()
{
this._factory =
new ChannelFactory<IContrato>(
new NetTcpBinding(),
“net.tcp://localhost:8722/srv”);
this._proxy = this._factory.CreateChannel();
}
private void button1_Click(object sender, EventArgs e)
{
string parametro = “algum valor”;
Func<string, string> executor = p => this._proxy.FazAlgo(p);
executor.BeginInvoke(
parametro,
result =>
{
this.textBox1.Invoke(
new Action<string>(valor => this.textBox1.Text = valor),
((Func<string, string>)result.AsyncState).EndInvoke(result));
},
executor);
}
A diferença que vemos no código acima, é que invocamos o método FazAlgo através de um delegate. Ao invés de criarmos delegates a todo momento para uma necessidade específica, podemos recorrer aos delegates expostos pelo .NET Framework Action<> e Func<>. No caso acima, estamos fazendo uso do Func<string, string>, que coincide com a assinatura do método FazAlgo, ou seja, recebe e devolve uma string. No exemplo acima, estamos fazendo o uso de lambda ao invés de explicitamente criar a instância do delegate.
Todos os delegates dão suporte à chamada assíncrono para método que ele mantém a referência, e sendo assim, via BeginInvoke disparamos a execução da operação do serviço, utilizando uma segunda thread, e mantendo a aplicação disponível para outros trabalhos. Neste caso, o método BeginInvoke recebe três parâmetros: o primeiro é o parâmetro de entrada que o serviço recebe; o segundo é um delegate de callback, que será invocado quando o resultado voltar; e finalmente, o terceiro parâmetro é um System.Object que será passado para o callback, e que estará acessível através da propriedade AsyncState, e que no caso, passamos a instância do delegate criado para invocar o método.
Para recuperar o resultado, dentro do callback invocamos o método EndInvoke, que retornará o resultado do serviço, e como na maioria dos casos, precisamos exibir isso na tela. Como o callback sempre é disparado na thread que está executando o método assíncrono, você não pode tocar nos controles, já que eles tem afinidade com a thread de criação deles. Aqui entra em cena o método Invoke, exposto pelo do controle que deseja atualizar, e através dele, determinamos um método para ser executado na mesma thread que o criou, e aqui também utilizando lambda.
Se você tiver acesso ao contrato e poder alterá-lo, então você pode dar suporte ao processamento assíncrono ao mesmo, e grande parte do trabalho que vimos acima, do lado do cliente, será descartado. Para suportar o processamento assíncrono no contrato, temos que criar as versões assíncronas do método, e com isso, o nosso contrato de exemplo ficará da seguinte forma:
[ServiceContract]
public interface IContrato
{
[OperationContract]
string FazAlgo(string value);
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginFazAlgo(string value, AsyncCallback callback, object state);
string EndFazAlgo(IAsyncResult result);
}
Ao contrário do que vimos anteriormente, ao utilizar essa técnica, você terá o processamento assíncrono tanto do lado do cliente, quanto do lado do serviço. Eu já discuti bastante sobre isso nestes outros artigos. Sendo assim, toda a complexidade da chamada assíncrona será movida para o serviço, que deveremos criar a versão assíncrona do método FazAlgo. O código do lado do cliente ficará relativamente mais simples:
private void button2_Click(object sender, EventArgs e)
{
string parametro = “algum valor”;
this._proxy.BeginFazAlgo(
parametro,
result =>
{
this.textBox1.Invoke(
new Action<string>(valor => this.textBox1.Text = valor),
this._proxy.EndFazAlgo(result));
}, null);
}
Para ajudar no entendimento, você pode baixar o código de exemplo clicando aqui.
Muito bom o tópico Israel, tirou várias dúvidas.
Eu (particularmente) gosto mais de códigos explícitos e não sei se utilizaria tanto o lambda, mas é bom ter conhecimento sobre.
De qualquer forma, como seria para linkar um método do formulário ao EndInvoke do serviço?
Seria possível? Abraços
Boas Leandro,
As vezes, o código explícito torna a leitura muito mais difícil. Apesar de lambdas serem açúcar sintático, o código fica muito mais fácil e simples.
De qualquer forma, eu não entendi a sua dúvida. O EndInvoke é um método. O delegate é o callback, que é o método que será disparado quando o processo assíncrono for finalizado.
Então Israel, minha idéia era ter um formulário com um botão salvar por exemplo.
Ao clicar no botão salvar, eu iria executar um form.enabled = false (evitando outras ações) e chamaria um proxy para este formulario como por exemplo: proxyPedido.SalvarPedido(pedido);
No proxy pedido, ele teria um beginSalvar e um EndSalvar. Eu gostaria que ao final do salvar, o método EndSalvar de alguma maneira, avisasse o formulario para que o formulário desse um form.enabled = true. Deu pra entender a confusão?
Eu preciso travar as telas, mas não o aplicativo inteiro. Por isso pensei em fazer tudo isso de forma assincrona.
Abraços e muito obrigado
Boas Leandro,
É exatamente isso que eu faço no exemplo. A única diferença é que quando o processamento assíncrono finaliza, eu mostro o resultado no TextBox, e você tem que habilitar o botão.