Comunicação assíncrona waitforexit
Esta questão já tem uma resposta aqui: Eu preciso gerar um processo filho que é um aplicativo de console e capturar sua saída. Eu escrevi o seguinte código para um método: No entanto, isso não retorna nada. Eu não acredito que o evento OutputDataReceived está sendo chamado de volta, ou o comando WaitForExit () pode estar bloqueando o segmento para que ele nunca será de retorno de chamada. EDIT: Parece que eu estava tentando muito com o retorno de chamada. Fazendo: parece funcionar bem. perguntou Nov 12 08 às 23:14 marcado como duplicado por Lu Vnh Ph250c. cutucar c Os usuários com o crachá c podem, individualmente, fechar as perguntas como duplicatas e reabri-las conforme necessário. 17 de out at 10:58 Esta pergunta foi feita antes e já tem uma resposta. Se essas respostas não responderem completamente à sua pergunta, faça uma nova pergunta. Se você não estiver interagindo com o aplicativo e apenas se preocupando com sua saída, você não deve usar a maneira Async BeginOutputReadLine () e Start () de fazê-lo. Eu encontrei estes para não ser muito confiável, e eles podem, por vezes, truncar o início da saída do aplicativo. ndash Michael Graczyk Jul 16 12 at 23:47 Não é que um impasse esperando para acontecer MSDN Docs dizer que você arrisca um deadlock se ouvir tanto a saída e erro ao mesmo tempo. O aplicativo irá parar se o buffer de erro estiver cheio e aguardar o esvaziamento. Mas você não está esvaziando o buffer de erro até que o buffer de saída esteja finalizado (o que não acontecerá quando o aplicativo estiver aguardando o buffer de erro). ndash Michael Bisbjerg Mar 31 13 at 15:42 A resposta de Judah não funcionou para mim (ou não está completa) como o aplicativo estava saindo após o primeiro BeginOutputReadLine () Isso funciona para mim como um trecho completo, lendo a saída constante de Um Ping: Introdução aos Tubos Justin Van Patten O CTP (Community Technology Preview) da Orcas October inclui novos tipos que facilitam o uso de canos do código gerenciado pelos desenvolvedores. Pipes são usados para comunicação entre processos (IPC) entre processos em execução na mesma máquina ou processos em execução em qualquer outra máquina Windows em uma rede. We8217ve adicionou suporte para pipes anônimos e nomeados. Os novos tipos de pipe podem ser encontrados em System. Core. dll dentro do namespace System. IO. Observe que, depois que o CTP de outubro foi lançado, nós movemos os tipos de pipe de System. IO para System. IO. Pipes, para que os tipos estivessem no novo namespace em todos os futuros CTPs. Canais anônimos Canais anônimos são baseados em caracteres e são half-duplex. Eles não podem se comunicar pela rede e suportam apenas uma única instância do servidor. Esses pipes são mais úteis para comunicação entre encadeamentos ou entre processos pai e filho, nos quais as alças de pipe podem ser facilmente passadas quando o processo filho é criado. Exemplo 1: Anonymous Pipes O exemplo a seguir demonstra o envio de uma string de um processo pai para um processo filho. Uma linha é lida no console do pai e enviada para o processo filho. O processo filho, em seguida, grava a seqüência de caracteres que recebeu do processo pai para o console. Process process new Processo () usando (AnonymousPipeServerStream pipeStream new AnonymousPipeServerStream (PipeDirection. Out, HandleInheritability. Inheritable)) Canais anônimos são doces Echo: Canais anônimos são doces Named Pipes Os pipes nomeados são muito mais poderosos do que os pipes anônimos. Eles podem ser duplex, pela rede e podem suportar várias instâncias de servidor de um único nome, tornando-os ideais para servidores multithread de fácil instalação e conexão rápida. Além disso, eles suportam comunicação baseada em mensagens para que um processo de leitura possa ler mensagens de comprimento variável precisamente como enviadas pelo processo de escrita. Por fim, os pipes nomeados suportam a representação, permitindo que os processos de conexão usem seu próprio conjunto de permissões em servidores remotos. Exemplo 2: Pipes nomeados Nos casos em que o segundo processo não pode herdar o identificador de pipe, pipes nomeados podem ser usados. O exemplo a seguir demonstra o envio de seqüências de caracteres do processo do cliente para o processo do servidor. usando (NamedPipeServerStream pipeStream novo NamedPipeServerStream (8220testpipe8221)) usando (StreamReader sr novo StreamReader (pipeStream)) enquanto ((temp sr. ReadLine ()) nulo) Console. WriteLine (8220: 8221. DateTime. Now, temp) usando (NamedPipeClientStream pipeStream new NamedPipeClientStream (8220testpipe8221)) usando (StreamWriter sw novo StreamWriter (pipeStream)) enquanto ((temp Console. ReadLine ()) null) Exemplo 3: Pipes nomeados com mensagens Os pipes nomeados também suportam comunicação baseada em mensagens. Isso permite que um processo de leitura leia mensagens de comprimento variável precisamente como enviadas pelo processo de gravação. O exemplo a seguir exibe como essas mensagens são enviadas e lidas. UTF8Encoding codificação new UTF8Encoding () string message1 8220Named Pipe Message Exemplo.8221 string message2 8220Another Named Pipe Message Exemplo.8221 usando (NamedPipeServerStream pipeStream novo NamedPipeServerStream (8220messagepipe8221. PipeDirection. InOut, 1, PipeTransmissionMode. Message, PipeOptions. None)) // Letrsquos enviar duas mensagens. pipeStream. Write (bytes, 0, bytes. Length) por Brad Cox A Stepstone Corporation 75 Glen Road Sandy Hook, CT 06482 203 426 1875 TaskMaster, Objective-C, ICpak e Software-IC são marcas comerciais da The Stepstone Corporation. Introdução (toc) O TaskMaster suporta uma das várias camadas em uma arquitetura orientada a objeto multinível que a Stepstone vem construindo de uma forma bottom-up desde a sua criação. Este documento descreverá os tipos de sistemas programáveis pelo usuário que imaginamos, uma arquitetura de software multinível para criá-los, a função que o TaskMaster desempenha nessa arquitetura e como o TaskMaster se relaciona com camadas adjacentes, como C e o sistema operacional de plataformas. O TaskMaster é uma biblioteca de funcionalidade baseada em C que suporta multitarefa leve e manipulação de exceção, de tal forma que os aplicativos cliente são portáveis entre plataformas de hardware / software. Por portáteis, queremos dizer que os aplicativos clientes acessam os recursos do TaskMasters por meio de uma interface de programa de aplicativo (API) que é independente de plataforma. Essa API é suportada por uma camada de código dependente de plataforma (PDL) que adapta a API ao hardware e software do host (sistema operacional). Para plataformas que já suportam alguns dos recursos do TaskMasters (Mach, OS / 2), o PDL usa os recursos de plataformas. Para as plataformas que não possuem esses recursos (Unix, MS / DOS), o PDL as fornece manipulando diretamente o hardware (registradores de máquina) que é subjacente ao ambiente de tempo de execução C. Para as plataformas Sun Unix, que atualmente é a única plataforma para a qual o TaskMaster foi portado, a PDL é escrita em C com uma pequena sub-rotina (8 instruções) no montador. As dúvidas dos clientes são bem-vindas em relação ao TaskMaster, seja em sua plataforma Sun atual ou portado para plataformas adicionais. Entre em contato com K. K. Tan at 203 426 1875. Sistemas Programáveis pelo Usuário (toc) Os documentos complementares, Planejando a Revolução Industrial do Software e Há um Bullet 2 descrevem a visão de longo alcance por trás deste trabalho. Eles vêem a crise do software como um obstáculo para passar da Era da Produção para a Era da Informação, onde os computadores se tornam o veículo pessoal de todos os homens em uma rede global de informações, o Modelo T Ford da Era da Informação. Eles vêem a crise de software como sendo resolvida como a falta de operadora de telefonia já foi resolvida, tornando cada usuário de computador um programador. Claro, programador não vai significar o que faz hoje. A palavra adquirirá muitos significados diferentes apropriados às habilidades e interesses de diversas classes de usuários em diferentes níveis de uma arquitetura de software multinível. Por exemplo, Tom, Dick e Harry são programadores típicos com habilidades e interesses totalmente diferentes: Tom é um programador no sentido C dessa palavra. Tom desempenha o papel que as empresas de fabricação de silicone desempenham no hardware, fabricando componentes de silício de nível de gate e de bloco em componentes de nível de chip que outros com habilidades menos especializadas, como Dick, podem usar. As tecnologias de nível de gate e de bloco, seja em hardware ou software, são atividades fortemente acopladas, nas quais a otimização do produto, não de seu desenvolvedor, é primordial. Dick é um programador no sentido Smalltalk ou Objective-C da palavra. Dick desempenha o papel que os fornecedores de placas desempenham em hardware, montando componentes no nível de chip que Tom produz em componentes de nível de cartão que outros com habilidades ainda menos especializadas, como Harry, podem usar. As tecnologias de nível de chip, seja em hardware ou software, são atividades fracamente acopladas nas quais as preocupações do desenvolvedor, como capacidade de conexão, intercambiabilidade e capacidade de reutilização, são primordiais. As tecnologias de nível de chip suportam essa demarcação crucial entre tecnologias de fabricação altamente acopladas que somente especialistas altamente qualificados usam, como fundições de silício, e as tecnologias de montagem muito mais simples e frouxamente acopladas que não especialistas podem usar, como chaves de fenda e ferros de solda. Harry é um usuário final, um dos outros. Harry é um especialista em domínio de problemas, sem experiência especializada em software. O objetivo da arquitetura a ser discutida neste artigo é fazer de Harry um programador em nível de cartão precisamente no sentido limitado, mas muito real, de que os usuários finais são designers de hardware em nível de cartão quando escolhem quais cartões comprarão para personalizar um programa pessoal. computador. Harry poderia ser um funcionário de um escritório de seguros, um gerente de uma agência bancária ou, no exemplo a seguir, um proprietário usando um computador pessoal para administrar as finanças domésticas. Hoje, Harry pode estar usando um programa de finanças pessoais como o ManagingYourMoney (MYM) no Macintosh. Tais programas permitem que Harry administre seus ativos, passivos, receitas e despesas para fornecer uma leitura instantânea do valor pessoal líquido. O que os programas e sistemas atuais não fazem é ajudar essa classe de usuários a criar suas próprias soluções personalizadas para suas necessidades específicas do problema. O Macintosh não suporta a programação do usuário final. Por exemplo, suponha que alguns dos ativos de Harry estejam em ações, títulos e fundos mútuos, cuja contribuição para o patrimônio líquido varia diariamente. Quando Harry se cansa de digitar os preços das ações do jornal, ele pode se aventurar na Era da Informação programando seu emulador de terminal para adquirir informações sobre preços de um serviço de informações como a Compuserve eletronicamente. Embora Harry provavelmente nunca programaria em C ou mesmo Smalltalk, ele poderia chegar ao ponto de usar uma planilha como o Excel para gerenciar um banco de dados de tendências de preços ao longo do tempo, talvez usando as instalações de gráficos da Excels para mostrar as tendências graficamente. As soluções não programáveis de hoje são adequadas apenas enquanto Harry estiver persuadido de que todo esse mouse3 para abrir e fechar documentos, iniciar e parar aplicativos e recortar e colar números é uma grande melhoria na digitação de cada número manualmente Se Harry insistisse em um maneira de construir seu próprio aplicativo para computar, com um único clique, um gráfico de seu patrimônio pessoal à medida que muda ao longo do tempo, seus desejos excederam o que o Macintosh em particular e a indústria de software como um todo podem oferecer hoje. A solução prevista neste documento é mostrada na Figura 1. Figura 1: Um programa que pode ser criado por um usuário final, escrito em uma linguagem de programação orientada a objeto icônica não-processual em nível de cartão do tipo previsto neste documento. Essa figura é composta de ícones e dumps de tela do Macintosh para indicar que cada ícone se comporta individualmente como no Macintosh hoje em dia. No entanto, existem diferenças significativas. A imagem não é uma coleção de ícones do tipo "o que você vê é tudo", que só podem ser usados manualmente e (exceto para cortar e colar manualmente) individualmente. A figura é a listagem de origem de um programa que Harry escreveu em uma linguagem de programação orientada a objetos icônica em nível de cartão, cujos objetos são baseados nas facilidades de multitarefas leves deste artigo. Os ícones representam tarefas leves ou objetos no nível do cartão. Harry pode salvar seu programa Compute Net Worth como um novo componente de nível de cartão e usá-lo junto com os mostrados aqui. No entanto, Harry não construiu os mostrados nesta figura. Ele os comprou em outro lugar, de Dick, que os montou a partir de componentes de nível de chip fabricados por Tom. As tarefas estão inicialmente inativas, cada uma aguardando entrada nas setas recebidas. Quando o botão Compute Net Worth (em outra tela não mostrada aqui) é clicado, isso produz o sinal que o ícone do emulador de terminal está esperando. Isso aciona a tarefa que o ícone do emulador de terminal controla, que pode ser uma encarnação em nível de cartão de um programa como o SmartComII, para discar o telefone, fazer logon no Compuserve e digitar comandos para baixar informações financeiras. Os ícones de pesquisa de três cadeias são a forma de Harry lidar com o fato de que a Compuserve não fornece informações financeiras em um formato que sua planilha possa aceitar. Ele usou um processo de divisão de fluxo (o pequeno círculo preto) para enviar os dados a três processos de pesquisa de cadeia de caracteres e os programou para encontrar preços de ações específicos e formatá-los conforme as três planilhas de histórico esperam. E assim por diante. Programadores Unix reconhecerão isso como uma variação no esquema de pipes e filtros do Unix. Mas há uma grande diferença que meu uso de programas familiares Macintosh não traz claramente. Ao contrário dos pipes Unix, que podem carregar apenas fluxos de bytes, as setas nesta figura podem transportar fluxos de objetos no nível do chip. Ao contrário das planilhas do Macintosh que aceitam somente texto, os programas nesta figura são gravados para aceitar dados expressos como objetos. Os objetos podem representar qualquer tipo de dados estruturado, incluindo estruturas baseadas em ponteiro, como listas e árvores. A seta que representa o fluxo de entrada para o ícone da planilha não precisa ser cadeias delimitadas por tabulações que a planilha deve analisar e reformatar internamente. A seta para o objeto de planilha poderia transportar um fluxo de objetos. instâncias da classe Record, não um fluxo de caracteres delimitado por tabulação, como as planilhas exigem hoje. Arquitetura de software multinível (toc) Este documento usará essa palavra-chave da moda, mas mal compreendida, com exatamente a frouxidão de significado que essa palavra tem no hardware. Ou seja, o objeto não terá qualquer significado técnico, a menos que esteja qualificado para tornar claro o contexto arquitetônico. Assim como um objeto de hardware em nível de porta não tem nada em comum com um objeto de hardware em nível de rack, cartão ou rack, também será com os diversos objetos de software na arquitetura de software multinível. A arquitetura foi motivada pelo reconhecimento pelas arquiteturas multiníveis de engenharia de hardware, em que objetos de hardware de alto nível, como equipamentos de escritório, são construídos de objetos de nível inferior, como cartões, objetos de nível de cartão de objetos de nível inferior, como chips de silício. objetos no nível do chip de objetos de nível inferior, como blocos de silício e portas. Espera-se que a solução para a crise do software evolua como no hardware, estratificando usuários / programadores de acordo com os níveis de habilidade e interesses, assim como o mercado de suprimento de encanamentos é estratificado em proprietário, encanadores, lojas de varejo, fábricas, refinarias e minas. Os usuários finais, como Harry, usarão tecnologias de modularidade / vinculação simplificadas suficientes para categorias limitadas de problemas, como os objetos de nível de cartão descritos aqui. Eles irão adquirir os objetos necessários subcontratando o trabalho para trabalhadores mais especializados, como Tom e Dick. A Figura 2 mostra os cinco níveis dessa arquitetura. Embora os objetos em todos os níveis atendam à definição clássica de objetos como unidades inseparáveis de estado e comportamento, os mecanismos de modularidade / vinculação pelos quais isso é feito são totalmente diversos nos diferentes níveis: Objetos de nível de bloco e de bloco são a expressão e Objetos em nível de sub-rotina de linguagens de programação convencionais como Cobol, Pascal e C e de linguagens orientadas a objeto fortemente acopladas como Ada e C. Com modularidade em nível de porta (expressões, macros, etc.), a ligação entre objetos é feita inteiramente em tempo de compilação pelo compilador. Modularidade de nível de bloco (bibliotecas de sub-rotina), atrasa a ligação até o momento do link, onde o trabalho ocorre no vinculador. Objetos de nível de chip são objetos fracamente acoplados, vinculados dinamicamente, como apresentados em linguagens como Smalltalk e Objective-C. Com a modularidade no nível do chip, a ligação é atrasada até o último momento possível. A ligação é feita pelo próprio programa no interesse de baixo acoplamento, reusabilidade, intercambiabilidade e capacidade de conexão. Todas as camadas subsequentes compartilham essa propriedade de ligação tardia, embora usando uma diversidade de meios para realizá-la. A figura mostra que C, ao contrário de Ada, fornece suporte limitado para programação em nível de chip em seu mecanismo de função virtual, uma forma de vinculação dinâmica. O suporte Cs para acoplamento flexível é limitado por sua ênfase em acoplamento rígido, verificação de tipo de tempo de compilação baseada em herança e ligação estática. Os objetos de nível de placa são os objetos no nível de tarefa de sistemas de multitarefa leves como Fabrik, Metaphor e Capataz. Eles serão o foco principal deste artigo. A modularidade em nível de cartão estende o significado tradicional de encapsulamento para também fornecer a cada objeto seu próprio controle, seu próprio relógio interno e senso de histórico, além de objetos de nível inferior, que mantêm seu estado e comportamento, mas não sua história. . Os objetos no nível do rack são a unidade de modularidade mais antiga e mais encapsulada de todos, a noção familiar de um programa como uma unidade de funcionalidade desenvolvida independentemente que escolhemos entre comprar um software na loja de software local ou escolher o programa a ser executado. um sistema de computador pessoal ou compartilhado no tempo. Figura 2: Orientado a objetos significa coisas diferentes em diferentes níveis de integração. Torta fatias representam a medida em que vários idiomas populares suportam o trabalho em cada nível. Assim como no hardware, o que distingue a modularidade em nível de chip da modularidade em nível de porta ou bloco é o acoplamento flexível, a vinculação dinâmica ou a capacidade de conexão. Enquanto as tecnologias de nível de porta e bloco são tecnologias de fabricação, as formas de fabricar as coisas a partir de princípios básicos, tecnologias de nível superior e de cavacos são formas de montar as coisas conectando componentes prontos de prateleira de bibliotecas de peças prontas para uso. Nenhum desses níveis são panacéias que eliminam a necessidade de outros níveis. Na escala de qualquer sistema significativo, os objetos de nível de chip gate, block e even são unidades extremamente pequenas de granularidade - grãos de areia nos quais os tijolos são necessários. Mas a modularidade em nível de rack dos sistemas operacionais, como Macintosh e Unix, vai longe demais na direção oposta. O rigoroso encapsulamento que torna a modularidade em nível de rack tão útil para empacotar pacotes de software como programas independentes, obstrui o livre intercâmbio de informações entre esses objetos de forma tão rigorosa que a programação do usuário final não é possível. A modularidade no nível de cartão fornece um meio termo intermediário entre os objetos de nível de rack altamente encapsulados de sistemas operacionais, como o Unix, e os objetos de nível de chip de linguagens de programação, como o Smalltalk. Exemplo: Fabrik e Smalltalk (toc) O relacionamento entre Fabrik4 e Smalltalk é um excelente exemplo de como os objetos no nível do cartão se relacionam com objetos de nível inferior. Fabrik foi escrito em Smalltalk, portanto, consiste internamente em objetos no nível do chip. Os programadores Smalltalk manipulam-nos, através da linguagem de programação Smalltalk, uma interface de usuário textual orientada a objetos, apropriada para especialistas em software, como Dick. A Fabrik projeta um tipo de objeto de nível superior na interface do usuário, um objeto no nível do cartão. Os não-programadores manipulam estes através da interface de usuário icônica da Fabriks, que é uma linguagem de programação não-textual adequada para não-especialistas como Harry. Do ponto de vista de Harry, esses novos objetos são intuitivamente atraentes, pois são passíveis de habilidades de raciocínio da vida cotidiana. Eles se comunicam, não de forma síncrona, por meio de invocação processual (algo que somente os programadores acham natural), mas por um modelo assíncrono que está mais relacionado com a maneira como os objetos familiares da experiência cotidiana se comportam. Os objetos de nível de cartão encapsulam uma cópia do encadeamento de controle de máquinas em uma placa de software, junto com os objetos no nível de chip usados para criar essa placa. Eles são objetos do tipo que os programadores chamam de objetos de processos leves que operam como corrotinas um do outro, não como sub-rotinas. Eles se comunicam de forma assíncrona enviando objetos no nível do chip por meio de objetos do canal de comunicação, como fluxos. Como as placas de software parecem rodar simultaneamente, sua interface de usuário é intuitivamente única. Por essa razão, eles são mais orientados a objetos do que as linguagens processuais e de thread único de hoje. Como os objetos tangíveis da experiência cotidiana, os objetos de nível de cartão fornecem seu próprio thread de controle internamente, eles não se comunicam por invocação processual, não suportam herança e sua interface de usuário é icônica, não textual. Objetos em nível de cartão são semelhantes a pipes e filtros Unix Eles compartilham a mesma noção não processual de processos simultâneos que se comunicam por meio de objetos ou canais de canais de comunicação assíncronos. Eles diferem em que os objetos no nível do cartão são processos leves. Eles compartilham um espaço de endereço comum e, portanto, não estão restritos à comunicação de fluxos de valores ou bytes. Os fluxos entre eles podem ser fluxos de referência que podem conter ponteiros incorporados, como listas vinculadas ou árvores. Considerando que os pipes só carregam bytes, os fluxos podem transportar objetos. Exemplo: Metaphor (toc) Metaphor5 é um exemplo relacionado com diferenças significativas. Fabrik e Metaphor são similares, pois ambos suportam uma linguagem de programação icônica voltada para não-programadores. Ambos são baseados em um paradigma de programação de bolhas e setas não processuais, como o paradigma Unix pipes and filters. Ambos diferem do Unix em que as bolhas podem trocar fluxos de referência (estruturas baseadas em ponteiros), como objetos, em vez de apenas fluxos de valor, como com pipes Unix. A diferença significativa é a ausência da arquitetura aberta que a Fabrik adquire de se basear em um padrão aberto como o Smalltalk. É meu entendimento, baseado em informações possivelmente falhas6. que a Metaphor é agora, ou era originalmente, baseada em hardware proprietário e em um sistema operacional proprietário. Isso implica a ausência de um padrão aberto para o nível de arquitetura correspondente ao Unix (integração em nível de rack). Como o Metaphor foi desenvolvido antes que as linguagens de programação orientadas a objetos se tornem comuns, não há nada, além dos padrões internos de codificação, para suportar o papel no nível de chip que os objetos Smalltalks desempenham no Fabrik. Se rumores persistentes são verdadeiros de que alguns ou todos os objetos de nível de cartão de Metáforas são codificados em assembler, até mesmo os padrões abertos de nível de porta e bloco que os programadores C tomam como garantidos (sub-rotinas e expressões) estão ausentes ou mais plausivelmente são fornecidos somente por padrões internos fechados. Eu entendo que existem padrões internos de codificação, pelos quais objetos de nível de cartão de metáforas se comunicam por uma representação tabular orientada para dados de planilha. Eu entendo que a Metáfora agora vê essa representação como um obstáculo para o crescimento contínuo, presumivelmente porque a dificuldade de forçar os dados não-tabulares que ocasionalmente se repetem nos aplicativos do cliente se ajustam ao modelo de dados tabulares da Metaphors. Entendo que aliviar essa e outras barreiras, como a não-portabilidade, é a motivação para a recente joint-venture entre a Metaphor ea IBM, Patriot Partners. A abordagem em várias camadas defendida neste documento aborda os problemas que a Metáfora encontrou ao tentar representar todos estruturas de dados de nível com uma única representação padrão fechada para dados tabulares. Uma linguagem de programação orientada a objetos no nível do chip, como o Objective-C, fornece um padrão aberto para representar dados estruturados. Eles permitem que os programadores desenvolvam novas representações de dados definindo novas classes de objetos, usando recursos orientados a objetos, como encapsulamento, herança e polimorfismo. As linguagens são suportadas por ferramentas de programação de última geração, como navegadores e por bibliotecas abrangentes de classes confiáveis, prontas para uso e pré-testadas. Como os objetos no nível de chip são, por definição, pouco acoplados, conectáveis, polimórficos e dinamicamente verificados, os objetos recém-criados podem ser imediatamente reconhecidos e processados por aplicativos preexistentes sem a necessidade de recompilar ou modificar os aplicativos. Exemplo: A experiência Stepstones TaskMaster e Objective-C (toc) com o Objective-C é um exemplo final do porquê de uma arquitetura de software multinível ser desejável. Enquanto a experiência de Metáforas mostra que programadores habilidosos precisam de padrões arquiteturais de nível inferior aos objetos de nível de cartão Metaphors, a experiência Stepstones mostra que os usuários precisam de objetos de nível mais alto do que objetos textuais e síncronos de objetos Objective-C que se comunicam enviando mensagens uns para os outros. onde o receptor calcula como uma sub-rotina do remetente, em vez de uma co-rotina. Os principais conceitos que distinguem os sistemas de alto nível programáveis pelo usuário das linguagens de programação especializadas de hoje, dos quais Objective-C é um exemplo típico, são interfaces de usuário icônicas (em vez de linguagem de programação textual) e concorrentes (assíncronas, não processuais) objetos que operam como coroutines um do outro, não sub-rotinas. A Stepstone não tomou, e é improvável que tome, a etapa final da construção de sistemas programáveis pelo usuário. Vemos nosso papel como provedor de componentes horizontais que os clientes usarão para criar soluções verticais para o usuário final. O ICpak 101 é uma biblioteca de classes de base que tendem a ser usadas em quase todas as aplicações. O residente mais usado dessa biblioteca é a classe Object, que como a classe raiz da maioria das hierarquias de herança fornece funcionalidade a todas as outras classes, como o mecanismo storeOn: / readFrom: para converter automaticamente objetos (fluxos de referência) em um caractere representação (fluxo de valor) para armazenamento ou transmissão através de redes ou tubulações. Essa biblioteca também fornece classes de estrutura de dados comumente úteis, como Conjuntos, Dicionários, Arrays, Listas e Strings. Esta biblioteca não é vendida separadamente, mas é fornecida com o compilador. O ICpak 201 é uma biblioteca de classes muito maior para criar interfaces de usuário icônicas de maneira independente de plataforma. Sua portabilidade também se deve a uma interface API / PDL distinta. Em muitas plataformas, a PDL do ICpak 201 é baseada no X-Windows, mas esta PDL pode ser, e tem sido, baseada em outras bibliotecas gráficas, incluindo hardware gráfico simples. TaskMaster é uma biblioteca de classes para construir objetos de nível de cartão assíncrono que operam como corrotinas um do outro, não sub-rotinas. Essas bibliotecas são baseadas no Objective-C, que a Stepstone também comercializa como a tecnologia de ligação de nível de chip fracamente acoplada para fornecer bibliotecas de grande porte que são suficientemente fracamente acopladas ao seu ambiente e que podem ser usadas em diversas aplicações do cliente. Objective-C é uma linguagem de programação orientada a objetos híbrida que suporta o modelo de objeto de nível de chip Smalltalk como uma extensão estritamente compatível com os objetos de nível de bloco e de porta de uma linguagem de programação convencional como C ou C: Users of Stepstones Objective O compilador - C, que é baseado em ANSI C, expressa operações de nível de porta e bloco em C. Uma versão baseada em C é planejada, mas não está disponível no momento. Os usuários do compilador NeXTs Objective-C, que é baseado no GNU C, expressam operações no nível de gate e block em C ou C. A Stepstone não se vê como um fornecedor de linguagem, apesar de um de nossos produtos mais populares ser um compilador para a linguagem de programação Objective-C. Nós nos vemos como fornecedores de componentes de software, com a linguagem como tecnologia capacitadora para colagem de componentes. Não vemos o Objective-C competindo com outras linguagens de programação populares, sejam elas C, Ada, Smalltalk, C ou o shell Unix. Objetivo-C é uma extensão para C. Na medida em que C é um C melhor, é uma plataforma melhor que C para construir um Objetivo-C melhor. TaskMaster Descrição Técnica (toc) Tanto para o destino. O restante deste documento é uma visita a um veículo para ir até lá, da cabine até a sala das caldeiras. Ele mostrará como os sistemas de nível de cartão programáveis pelo usuário podem ser montados a partir de componentes de software em nível de chip da Objective-C, e estes a partir das tecnologias de gate e block de C ou C. Como a apresentação será progressivamente mais estreita e mais técnica a partir daqui, aqueles desinteressados em questões de sala de caldeiraria podem desejar encaminhar as partes mais técnicas para um especialista. O objetivo do TaskMasters é fornecer um modelo de objeto multitarefa leve com sobrecarga baixa o suficiente para que os programadores não fiquem relutantes em usá-lo tão extensivamente quanto usam chamadas e mensagens de sub-rotina hoje. Como as tarefas leves compartilham o mesmo espaço de endereço, elas podem trocar livremente dados estruturados e intensivos de ponteiro, como objetos Objective-C. Um sinal dessa filosofia de baixa sobrecarga é que a maioria das instalações do TaskMasters é acessível por meio de um par de APIs. Além de uma interface de transmissão de mensagens resultante da implementação de TaskMasters como uma biblioteca de classes Objective-C, cada método de classe também é empacotado para que eles também possam ser chamados de sub-rotinas comuns, permitindo assim que programas C invoquem a funcionalidade completa do TaskMasters. ausência de Objective-C. Em outras palavras, TaskMaster não é específico para Objective-C, e pode ser usado por programas C comuns. No entanto, para maior clareza no que segue, descreverei o TaskMaster em termos de sua API Objective-C. Tarefas leves e alternância de contexto (toc) As suposições externas dos programas comuns C (e Objetivo-C) sobre seu ambiente de execução são bastante simples. Um programa se encontra carregado em um domínio de computador (um espaço de endereço), dividido em quatro códigos de arenas, dados, pilha e heap. A arena de código contém o código dos programas, a arena de dados contém os dados estáticos dos programas e as arenas de pilha e heap estão inicialmente vazias. O contador de programa de máquinas (PC) aponta para o endereço inicial do programa na arena de código e seu ponteiro de pilha (SP) aponta para a pilha, que é inicialmente vazia, exceto para argumentos de linha de comando (argc, argv e similares). Registros adicionais também estão envolvidos, mas, desde que sejam salvos e restaurados corretamente durante a alternância de tarefas, será útil ignorá-los. Esta situação inicial é mostrada na Figura 3. Um domínio (talvez um dos vários em um sistema de compartilhamento de tempo) é mostrado adjacente a outros dois. O objeto de domínio é pré-inicializado para uso como código, dados e arenas de heap. Estes não são desenhados como regiões separadas, uma vez que são melhor entendidos como subdivididos entre os objetos desenhados como pequenos quadrados. A pilha inicial é o retângulo arredondado na parte inferior. The machines registers are shown at the bottom, initialized so that the PC points to the first instruction and the SP points to the first slot of the (empty) stack arena. When TaskMaster is linked into such a program, it is automatically invoked at start up time via Objective-Cs class initialization mechanism. The initialization allocates several TaskMaster objects to formalize the situation as objects. It creates an instance of Domain, theDomain . to model the address space as a whole, and an instance of Task, rootTask . to formalize the root task represented by the initial register settings. The initialization also initializes several global and static variables for its subsequent use, the primary ones being a currentTask variable, initialized to rootTask, and readyQueue . initialized to empty (since no other tasks exist yet). Additional tasks are created when rootTask requests it through any of a variety of ways, differing only in syntactic convenience. What follows will be presented in terms of a uniform syntactic mechanism, Actions . which are modeled after Smalltalks Block mechanism. The Action mechanism will be, but is not yet, supported by the Objective-C compiler. Until it is described later, think of actions as a more powerful, easier to use and understand, replacement for many of the things function pointers are used for in C. The simplest way to create additional tasks (remember, other ways are also provided, including invoking the task creation logic by ordinary C function calls) is to send a fork message to an instance of Action. Actions incorporate a C function that plays the role that main(argc, argv) played for the domain as a whole: aTask anAction fork The message returns a new instance of Task. This involves allocating a stack arena7 for the new task from the heap initialized such that, when the task is executed for the first time, the actions initial C subroutine will find its arguments in the normal fashion for any C subroutine. The newly created task is automatically linked onto the readyQueue for eventual execution. The rootTask might create several such tasks, sending messages and calling functions as it does so, thus modifying the stack space of rootTask. Figure 4 shows rootTasks situation at an arbitrary point in its computation, three levels deep into a subroutine call or message send: Since TaskMaster does not provide preemptive scheduling, rootTask will remain in control until it performs some action that will put it to sleep and let one of the queued tasks proceed. Normally, a task does not do this by explicitly invoking the low-level primitives to be described here. It does so implicitly, by invoking a lightweight I/O operation on one of the I/O channel mechanisms described in the next section. The highest-level kernel entry that a user might access directly is the Semaphore class. Semaphores are a queue on which tasks can to wait for a resource and a count of available resources8. Suppose that some task does a lightweight read operation (anObject aStream get) on a communication channel, such as a stream, whose internal buffer is initially empty. Streams use semaphores to monitor the status of this buffer, so the emptySemaphores resource count variable will indicate that currentTask must wait for another task to add something to the buffer. Therefore, the semaphore will delay the task by linking it onto the semaphores queue of waiting tasks and call taskScheduler to put the current task to sleep and start another one running. The task scheduler chooses the topmost task in the readyQueue9 as nextTask, and calls the context switching routine as follows: contextSwitch(ampcurrentTask-gtregisters, ampnextTask-gtregisters, nextTask) This is where the magic happens. Whereas it was currentTask that called this function, a different task is running when it returns. This new task has its very own execution history (stack area) exactly as it was when the task was last put to sleep by contextSwitch. The magic is quite simple. A lightweight task is simply the machines execution state along with the call history that produced that state. Both are represented by the machines SP register. Calling a subroutine pushes these registers into the address in the SP register, and returning restores them from the address in the same register. The contextSwitch routine accomplishes its magic by saving the current tasks execution state, the SP register, in currentTask-gtregisters and loads another tasks SP from nextTask-gtregisters10. where the new task was saved when it was put to sleep. New tasks are handled the same way by initializing the stack arena and the tasks register save area such that contextSwitch will start the task as if its entry subroutine had been called via an ordinary subroutine call. The size of a tasks stack arena is established when the task is created, either with a reasonable default size or a size specified by the programmer. This arena cannot be enlarged thereafter because of deep-seated assumptions in Cs run-time model that are beyond TaskMasters control. If a task uses more than this amount, perhaps because of unforeseen recursion, the results would be unpredictable. Since providing a more robust subroutine-call mechanism in C is not practical, TaskMaster takes a less drastic approach. It initializes each stack arena with a block of guard information, which taskScheduler checks before every context switch to detect whether the task that has just finished running has overrun its allocation. Lightweight I/O (across tasks) (toc) The previous section showed how TaskMaster supports semaphores within an vanilla C environment, without depending on platform facilities whose absence would lead to non-portability. Semaphores are an extremely low-level scheduling primitive, too powerful for non-specialists in the same way that assembly language is too powerful. Although TaskMaster does provide semaphores, they should be regarded as a low-level mechanism for experts to use in building higher-level mechanisms that are not already available off-the-shelf. TaskMaster applications are concurrent applications (in the restricted sense of the previous section). Concurrently active tasks must be careful to synchronize the tasks access to shared data so that race conditions do not occur. The computer literature is full of ways for doing this, ranging from extremely low-level and general mechanisms like semaphores and event counts at the one extreme, to intermediate-level mechanisms like the Ada rendezvous mechanism at the other, to the ultra-high-level but ultra restrictive pipes and filters mechanism of Unix. The synchronization mechanisms that are most highly developed in TaskMaster today are oriented toward supporting the higher-level of object-oriented granularity that was described earlier as card-level integration libraries of loosely-coupled, iconic, concurrent modules that non-programmers can plug together (at runtime, not compile time) to create custom applications. The higher-level task mechanisms to be described next follow the Unix pipes and filters model, modified to assume lightweight tasks (card-level modules) that communicate by sending chip-level objects through lightweight communication channels such as streams11 . This philosophy departs from other workers in this field. For example, Grady Boochs library of Ada components takes a different approach, in which every reusable object (say, a Collection class), must allow for the fact that it may be accessed concurrently by multiple tasks. In practice, this means that every collection class must exist in multiple versions, one with guard logic to support concurrent access and others that do not for non-shared applications. Card-level objects reflect a different philosophy. Just as hardware cards do not share chips, software card-level objects (tasks) do not share chip-level objects such as collections. Rather, they communicate through specialized communication channel objects such as Streams a software analog for hardware busses between cards. Of course, hardware analogies are seldom perfect in software. As we shall see, the signals that these software buses carry are the same kind of chip-level objects that comprise the cards themselves. Whereas Booch would speak of two tasks sharing a common object, we view them as each owning the object at different times, passing it through a communication channel object that provides whatever sharing paradigm is needed. Streams are the simplest sharing paradigm of all I own it unless I give it to you. Since the stream sharing paradigm is simple to explain and to use, we expect that it will be the prevalent, but never the only, synchronization mechanism in user-programmable systems12. For example, Harrys program (Figure 1) is built entirely of task instances (icons) connected by stream instances (arrows). The large icons are tasks programmed to operate on objects flowing through streams and the small black dots represent generalized stream operations such as fork and join. Each of the tasks in such a system are programmed in a similar fashion, as a loop that reads objects from an incoming stream, processes them, and emits objects on an outgoing stream. For example, here is how a task might be coded in ordinary Objective-C, without using Action expressions: inObject s-gtinStream get process inObject compute outObject. The two streams in this example, inStream and outStream, may have been preallocated by the rootTask and passed to this task as formal parameters as argc and argv are passed to Unix programs. It is also possible to pass such information in global variables (all tasks share the same global address space), or to create them within the task itself. The following larger example creates three tasks that communicate via two streams13. using action expressions to avoid the syntactic clutter that would result had this been written using functions and function pointers: main(int argc, char argv, char envp) Stream stream1 Stream forObjects Stream stream2 Stream forObjects In this example, the rootTask is creating three sub-tasks, which are automatically enrolled them on the readyQueue for eventual execution. When this task executes the currentTask waitForExit. message, it will be suspended14 and the first task on the readyQueue, task1, will begin executing. The Stream forObjects message creates streams that, by default15. support buffered communication. In this respect, streams are similar to the formatted I/O of C stdio functions like fopen(), fread(), printf() and so forth. Stream I/O is buffered I/O, as distinct from the unbuffered I/O of primitives like open(), read(), write(), etc. Since task1s output is buffered, it will loop repeatedly, putting an additional object reference into stream1s internal buffer each time. Eventually the semaphore that stream1 uses to detect a full buffer will determine that task1 must wait. The semaphore will stop the task by linking it onto a queue for consideration once more room becomes available. The next task in the readyQueue (task2) will be scheduled to begin emptying stream1s buffer. And so forth. Heavyweight I/O (across domains) (toc) The preceding section has outlined an architecture in which card-level objects (tasks) communicate by a card-level communication mechanism (streams). And it has showed how card-level objects are constructed from the chip-level mechanisms of Objective-C and these, in turn, from the gate - and block-level mechanisms of C. Now we turn to showing how lightweight tasks couple with the heavyweight processes and I/O mechanisms of a time-sharing operating system, for which Unix is a typical example (Figure 4). Harrys program in Figure 1 contains many examples of where a lightweight task must synchronize with events outside the domain in which the task resides. Three examples are mouse clicks, activity on the modem or the keyboard, and disk I/O. The simplicity that makes Unix so convenient for C and shell programming makes it a worst-case environment for the kind of lightweight tasking extensions described here, far more difficult than environments like VMS or MS/DOS for example. Our success in implementing the TaskMaster PDL within the restrictive Unix environment shows that the TaskMaster API can be implemented on less restrictive platforms like OS/2, MS/DOS, Windows, Mach and other Unix platforms. This section will describe how the TaskMaster API deals with heavy-weight I/O, and then look beneath the covers at how this API was implemented under Sun Unix. The code within a card-level object (a task) should be independent of whether the task happens to be connected to another task, or to an external I/O device, which TaskMaster models as instances of a special class, Port. Whereas streams support a buffered I/O model as in Cs stdio library, ports model unbuffered I/O devices, as in I/O system calls. The logic for filling and emptying a streams buffer depends on whether the stream is connected to a task or to a Port. TaskMaster handles this by providing two stream classes. The Stream class is used only for connecting tasks to tasks. The PortStream class is used for connecting tasks to ports. The two streams offer the same message interface to their clients. By default under Unix, all I/O operations are blocking the whole domain is suspended until I/O is complete. This means that if any task within a domain invokes Unix kernel I/O directly, the kernel will block the domain and every sub-task until the operation has completed. Since this is usually not what is wanted, lightweight tasks avoid doing kernel I/O operations directly by using Streams (for buffered I/O) or Ports (for unbuffered I/O). Streams provide a message protocol that supports all stdio I/O operations, including formatted I/O as in printf(), and ports provide a similar protocol for all kernel I/O operations. Although invoking stdio or kernel I/O directly does no harm apart from the blocking behavior mentioned above, tasks generally use streams or ports for all I/O. Non-blocking I/O is supported by the Port and Sensor classes. The Port class tracks the tasks that have I/O outstanding and schedules them so that other tasks can run while the I/O is pending. It is supported by Sensor, a platform-dependent class in the TaskMaster PDL, whose sole instance, theSensor . provides platform-specific logic for non-blocking I/O. Under Sun Unix, Sensor is based on the Unix select(2) system call. A complication is that streams can carry either references (objects) or values (characters) to other tasks in the same domain, whereas PortStreams can only pass values to Ports. This arises from the fundamental limitation that card-level (lightweight) processes can exchange references (pointers) to chip - and lower-level objects freely, while rack-level (heavyweight) processes can only exchange values (character strings). The PortStream class helps to hide, but of course can never eliminate, the distinction between lightweight and heavyweight I/O16. It implements its reference-stream I/O methods (put: and get) in terms of the storeOn: and readFrom: methods that all objects inherit from the Objective-C root class, Object. The put:method uses storeOn: to represent the argument (and everything it references) as a string of ASCI characters. The get method uses the complementary method, readFrom. to reconstruct the object from this encoding. Exception handling (toc) Consider a subroutine, main(), which calls foo(), which calls bar(). The runtime stack that underlies the C runtime environment extends to record that main has called foo and that foo has called bar. Then it retracts as bar returns to foo and as foo returns to main. The same call/return path is used unconditionally, regardless of whether the subroutines ran successfully or failed because of an exception. The absence of a way of handling exceptions explicitly, independently from normal processing, is a severe obstacle to software quality and reusability. Since subroutines routines return the same way, regardless of whether they succeed or fail, a method that should return a handle to a new object might instead return an error code to indicate that it could not, perhaps because of insufficient memory. Since any such call might fail, the caller must check every return value, reporting any failures to higher levels with similar means. This is sufficiently tedious that it is neglected, allowing unhandled exceptions to crash the application. An exception is a situation where a computation does not proceed as planned, perhaps because of I/O errors, inability to allocate a new object because of insufficient memory, or defects in the software itself. Exception handling is a language/environmental feature that reserves the normal subroutine/message return channel exclusively for normal processing. Routines that return normally can be assumed to have succeeded, since those that fail will return via a independent channel reserved for exceptions. Low-level routines never fail by returning an error code for the caller to decipher, but by raising an exception. Higher level routines establish code fragments, exception handlers, that will receive control if the exception they are to handle is raised by a lower-level routine. The following shows how exception handling might be done in C, in order to show the limitations of this solution and how these limitations are addressed in TaskMaster. TRY() is a C macro that uses setjmp() to record the machines register settings before entering the computation that might fail the foo subroutine. handle low memory exceptions. handle IO failure exceptions.
Comments
Post a Comment