C# - Streams síncronos e assíncronos


Você precisa entender como funciona a estrutura de I/O na plataforma .NET para poder trabalhar com arquivos, streams, sockets, criar aplicações para trabalhar em redes, realizar operações com arquivos, serialização, codificar/decodificar dados, etc.

A fim de proporcionar uma interface de programação similar para a ampla gama de dispositivos I/O com que um programador tem de lidar, uma arquitetura baseada em stream(fluxo de dados) foi desenvolvida na plataforma .NET.

Um Stream pode ser definido como um fluxo de dados em um sistema computacional. Quando um arquivo é aberto para edição, todo ele ou parte dele fica na memória, permitindo assim alterações, por isto somente quando ele é fechado, tem-se a garantia de que nenhum dado se perderá ou será danificado.

Quando um arquivo é carregado na memória para ser editado, esta carga ocorre num fluxo "stream", ou seja, linha a linha até o carregamento total do arquivo, como água a correr num cano ou bytes sendo lidos por um programa.
(
http://pt.wikipedia.org/wiki/Stream)

Os dispositivos de I/O podem ser qualquer coisa desde impressoras a discos rígidos e interfaces de rede; mas nem todos os dispositivos suportam as mesmas funções. Por exemplo, é possível ler somente a segunda metade de um arquivo de 1 MB, mas não é possível fazer o download apenas da segunda metade de uma página web.

Portanto, nem todos os streams suportam os mesmos métodos. Propriedades tais como canRead(), CanSeek(), e canWrite() indicam as capacidades do stream, quando aplicados a um dispositivo particular.

Os streams podem ser usados de duas maneiras: de forma assíncrona ou síncrona.

Ao usar um stream síncrono, após chamar um método, a thread vai parar até que a operação seja concluída ou falhar.

Quando se utiliza um stream de forma assíncrona, a thread retornará da chamada do método imediatamente, e sempre que a operação for concluída, um método será chamado para indicar a conclusão da operação, ou algum outro evento, como a falha de I/O.

Não é nada amigável ter um programa que "congela" quando se está esperando uma operação terminar. Portanto, chamadas de métodos síncronos devem ser utilizadas em uma thread separada.

Através da utilização de threads e chamadas de métodos síncronos, os computadores dão a ilusão de serem capaz de fazer várias coisas ao mesmo tempo. Na realidade, a maioria dos computadores têm apenas uma unidade de processamento central (CPU), e a ilusão é conseguida alternando-se rapidamente entre as tarefas a cada poucos milissegundos.

O exemplo da aplicação a seguir ilustra as duas técnicas.

1- Usando Streams para arquivos

Inicie um novo projeto (File-> New Project) no Visual Basic 2010 Express Edition usando o template Windows Forms Application com o nome stream1_VBNET;

A partir da ToolBox arraste e solte os controles a seguir no formulário form1.vb:

Disponha os controles conforme o leiaute da figura abaixo:

Vamos definir as declarações dos namespaces usados no formulário:

Imports System.IO
Imports System.Text
Imports System.Threading

Agora no evento Load do formulário defina o código abaixo:

 Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Control.CheckForIllegalCrossThreadCalls = False
    End Sub

A propriedade CheckForIllegalCrossThreadCalls obtém ou define um valor indicando se vamos capturar chamada da thread incorreta que acessa a propriedade de Handle de um controle quando o aplicativo está sendo depurado.

A exceção lançada será: Cross-thread operation not valid: Control 'text Resultado' accessed from a thread other than the thread it was created on

Quando uma thread diferente da thread que criou um controle tenta acessar um dos métodos ou propriedades do controle, freqüentemente ocorrem resultados imprevisíveis.

Uma atividade de thread inválida comum é chamar a thread incorreta que acessa a propriedade Handle do controle.

Definir CheckForIllegalCrossThreadCalls como false evita que a exceção ocorra mas não resolve o problema, podendo a causar erros imprevisíveis na aplicação; por isso NUNCA use esse recurso em uma aplicação de produção. Eu o fiz para tornar mais simples o código do exemplo.

Para localizar mais facilmente e diagnosticar esta atividade de threads durante a depuração defina a propriedade como true que é o seu valor padrão.

Agora no evento Click do botão de comando - Acesso Assincrono - vamos definir o código abaixo:

 Private Sub btnAssync_Click(sender As System.Object, e As System.EventArgs) Handles btnAssync.Click
        ofdStream.ShowDialog()
        callback = New AsyncCallback(AddressOf fs_StateChanged)
        fs = New FileStream(ofdStream.FileName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, True)
        ReDim conteudoArquivo(fs.Length)
        fs.BeginRead(conteudoArquivo, 0, fs.Length, callback, Nothing)
    End Sub

Precisamos definir a rotina fs_StateChanges()

  Private Sub fs_StateChanged(ByVal asyncResult As IAsyncResult)
        If asyncResult.IsCompleted Then
            txtResultado.Text = Encoding.UTF8.GetString(conteudoArquivo)
            fs.Close()
        End If
    End Sub

Vamos entender o que foi feito:

Use um delegate AsyncCallback para processar os resultados de uma operação assíncrona em um thread separado. O delegate AsyncCallback representa um método de retorno de chamada que é chamado quando a operação assíncrona for concluída.

Agora vamos definir o código do evento Click para o botão - Acesso Síncrono - conforme o código a seguir:

 Private Sub btnSync_Click(sender As System.Object, e As System.EventArgs) Handles btnSync.Click
        Dim thdSyncRead = New Thread(New ThreadStart(AddressOf syncRead))
        thdSyncRead.Start()
        ofdStream.ShowDialog()
    End Sub

Este código não executa qualquer manipulação de arquivos, em vez disso, ele cria uma nova thread, cujo ponto de entrada é a rotina syncRead.

Quando esta thread for executada, ela o faz em paralelo com outro código que está em execução e isso inclui as funções do sistema operacional. Se o código acima foram substituído por uma simples chamada a syncRead() ,o programa continuaria a funcionar, no entanto, se o arquivo fosse muito grande, o usuário rapidamente perceberia que o aplicativo ficaria congelado.

Uma aplicação 'congelada' fica sem resposta e no caso ela ficaria esperando o termina da execução da thread na aplicação que esta ocupando todo o processamento.

O código da rotina syncRead() é dado a seguir:

 Public Sub syncRead()
        Try
            Dim fs As FileStream
            Try
                fs = New FileStream(ofdStream.FileName, FileMode.OpenOrCreate)
            Catch ex As Exception
                MessageBox.Show(ex.Message)
                Return
            End Try
            fs.Seek(0, SeekOrigin.Begin)
            ReDim conteudoArquivo(fs.Length)
            fs.Read(conteudoArquivo, 0, fs.Length)
            txtResultado.Text = Encoding.UTF8.GetString(conteudoArquivo)
            fs.Close()
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try       

No código acima, você vai notar que o construtor FileStream é envolvido por um bloco try/catch. Isto permite que o programa se recupere de problemas como a falta de um arquivo ou um disco ilegível. em aplicações de produção, qualquer operação que depende da existência de arquivos ou de rede os recursos devem ser contido dentro de um bloco try/catch.

Executando o projeto e selecionando um arquivo iremos obter o resultado abaixo:

Pegue o projeto completo aqui: stream1_VBNET.zip

Luc 21:33 Passará o céu e a terra, mas as minhas palavras jamais passarão. (Jesus Cristo)

Joã 6:35 Declarou-lhes Jesus. Eu sou o pão da vida; aquele que vem a mim, de modo algum terá fome, e quem crê em mim jamais terá sede

Heb 13:8 Jesus Cristo é o mesmo, ontem, e hoje, e eternamente.

Jesus Cristo não é religião é uma pessoa, verdadeiro homem e verdadeiro Deus; é o Salvador.

Referências:


José Carlos Macoratti