Pergunta

Estou baixando alguns arquivos de forma assíncrona em uma grande matriz de bytes, e tenho um retorno de chamada que dispara periodicamente sempre que alguns dados são adicionados a essa matriz. Se eu quiser dar aos desenvolvedores a capacidade de usar o último pedaço de dados que foram adicionados à matriz, então ... bem, como eu faria isso? Em C ++, eu poderia dar a eles um ponteiro para algum lugar no meio e depois talvez dizer a eles o número de bytes que foram adicionados na última operação, para que pelo menos saibam o pedaço que deveriam estar olhando ... Eu realmente não Deseja dar a eles uma segunda cópia desses dados, isso é apenas um desperdício.

Só estou pensando se as pessoas desejam processar esses dados antes que o arquivo tenha concluído o download. Alguém realmente gostaria de fazer isso? Ou é um recurso inútil, afinal? Eu já tenho um retorno de chamada para quando o buffer (matriz inteira de bytes) está cheia e eles podem despejar tudo sem se preocupar com pontos de partida e final ...

Foi útil?

Solução

.NET tem uma estrutura que faz exatamente o que você deseja:

System.arraysegment.

De qualquer forma, também é fácil implementá -lo - basta fazer um construtor que leva uma matriz base, um deslocamento e um comprimento. Em seguida, implemente um Indexador Isso compensa os índices nos bastidores, para que seu Arraysegment possa ser usado perfeitamente no local de uma matriz.

Outras dicas

Você não pode dar a eles um ponteiro para a matriz, mas pode dar a eles a matriz e iniciar o índice e a duração dos novos dados.

Mas tenho que me perguntar para o que alguém usaria isso. Isso é uma necessidade conhecida? Ou você está apenas imaginando que alguém pode querer isso algum dia. E se sim, há alguma razão para que você não possa esperar para adicionar a capacidade quando o somone realmente precisar?

Se isso é necessário ou não, depende se você pode se dar ao luxo de acumular todos os dados de um arquivo antes de processá -los ou se você precisa fornecer um modo de streaming em que processa cada um pouco a ele. Isso depende de duas coisas: quantos dados existem (você provavelmente não gostaria de acumular um arquivo multi-gigabyte) e quanto tempo leva o arquivo para chegar completamente (se você estiver obtendo os dados em um link lento, você pode não quero que seu cliente espere até que tudo chegasse). Portanto, é um recurso razoável a ser adicionado, dependendo de como a biblioteca deve ser usada. O modo de streaming é geralmente um atributo desejável, então eu votaria na implementação do recurso. No entanto, a idéia de colocar os dados em uma matriz parece errada, porque implica fundamentalmente um design não transmitido e porque requer uma cópia adicional. O que você poderia fazer é manter cada pedaço de dados de chegar como uma peça discreta. Estes podem ser armazenados em um recipiente para o qual a adição no final e a remoção da frente é eficiente.

Copiar um pedaço de uma matriz de bytes pode parecer "desperdício", mas, novamente, idiomas orientados a objetos como C# tendem a ser um pouco mais inúteis do que os idiomas processuais de qualquer maneira. Alguns ciclos extras da CPU e um pouco de consumo extra de memória podem reduzir bastante a complexidade e aumentar a flexibilidade no processo de desenvolvimento. De fato, copiar bytes para um novo local em memória para mim parece um bom design, em oposição à abordagem do ponteiro, o que dará a outras classes acesso a dados privados.

Mas se você deseja usar ponteiros, o C# os suporta. Aqui está um tutorial de aparência decente. O autor está correto quando afirma: "... os ponteiros só são realmente necessários em C#, onde a velocidade de execução é altamente importante".

Eu concordo com o OP: Às vezes, você simplesmente precisa prestar atenção à eficiência. Não acho que o exemplo de fornecer uma API seja o melhor, porque isso certamente exige inclinar -se para a segurança e a simplicidade sobre a eficiência.

No entanto, um exemplo simples é ao processar um grande número de enormes arquivos binários que possuem zilhões de registros, como ao escrever um analisador. Sem usar um mecanismo como o System.arraysegment, o analisador se torna um grande porco de memória e é bastante desacelerado criando um zilhão de novos elementos de dados, copiando toda a memória e fragmentando o rebaixamento da pilha. É uma questão de desempenho muito real. Escrevo esses tipos de analisadores o tempo todo para coisas de telecomunicações que geram milhões de registros por dia em cada uma das várias categorias de cada um dos muitos comutadores com estruturas binárias de comprimento variável que precisam ser analisadas em bancos de dados.

Usando o mecanismo do sistema. ARRAYSEGEM, versus criar novas cópias de estrutura para cada registro acelera tremendamente a análise e reduz bastante o consumo de memória de pico do analisador. Essas são vantagens muito reais, porque os servidores executam vários analisadores, executam -os com frequência e conservação de velocidade e memória = economia de custos muito real em não ter que ter tantos processadores dedicados à análise.

O segmento System.Array é muito fácil de usar. Aqui está um exemplo simples de fornecer uma maneira básica de rastrear os registros individuais em um grande arquivo binário típico, cheio de registros com um cabeçalho de comprimento fixo e um tamanho de registro de comprimento variável (controle óbvio de controle de exceção excluído):

public struct MyRecord
{
    ArraySegment<byte> header;
    ArraySegment<byte> data;
}


public class Parser
{
    const int HEADER_SIZE = 10;
    const int HDR_OFS_REC_TYPE = 0;
    const int HDR_OFS_REC_LEN = 4;
    byte[] m_fileData;
    List<MyRecord> records = new List<MyRecord>();

    bool Parse(FileStream fs)
    {
        int fileLen = (int)fs.FileLength;
        m_fileData = new byte[fileLen];
        fs.Read(m_fileData, 0, fileLen);
        fs.Close();
        fs.Dispose();
        int offset = 0;
        while (offset + HEADER_SIZE < fileLen)
        {
            int recType = (int)m_fileData[offset];
            switch (recType) { /*puke if not a recognized type*/ }
            int varDataLen = ((int)m_fileData[offset + HDR_OFS_REC_LEN]) * 256
                     + (int)m_fileData[offset + HDR_OFS_REC_LEN + 1];
            if (offset + varDataLen > fileLen) { /*puke as file has odd bytes at end*/}
            MyRecord rec = new MyRecord();
            rec.header = new ArraySegment(m_fileData, offset, HEADER_SIZE);
            rec.data = new ArraySegment(m_fileData, offset + HEADER_SIZE,   
                          varDataLen);
            records.Add(rec);
            offset += HEADER_SIZE + varDataLen;
        } 
    }
}

O exemplo acima fornece uma lista com a ArraySegments para cada registro no arquivo, deixando todos os dados reais em uma grande matriz por arquivo. A única sobrecarga são os dois segmentos de matriz no MyRecord Struct por registro. Ao processar os registros, você tem as propriedades MyRecord.Header.Array e MyRecord.data.array, que permitem operar os elementos em cada registro como se fossem suas próprias cópias de byte [].

Eu acho que você não deveria se incomodar.

Por que diabos alguém iria querer usá -lo?

Parece que você quer um evento.

public class ArrayChangedEventArgs : EventArgs {
    public (byte[] array, int start, int length) {
        Array = array;
        Start = start;
        Length = length;
    }
    public byte[] Array { get; private set; }
    public int Start { get; private set; }
    public int Length { get; private set; }
}

// ...
// and in your class:

public event EventHandler<ArrayChangedEventArgs> ArrayChanged;

protected virtual void OnArrayChanged(ArrayChangedEventArgs e)
{
    // using a temporary variable avoids a common potential multithreading issue
    // where the multicast delegate changes midstream.
    // Best practice is to grab a copy first, then test for null

    EventHandler<ArrayChangedEventArgs> handler = ArrayChanged;

    if (handler != null)
    {
        handler(this, e);
    }
}

// finally, your code that downloads a chunk just needs to call OnArrayChanged()
// with the appropriate args

Os clientes se conectam ao evento e são chamados quando as coisas mudam. É isso que a maioria do código do cliente em .NET espera ter em uma API ("Ligue para mim quando algo acontecer"). Eles podem conectar -se ao código com algo tão simples quanto:

yourDownloader.ArrayChanged += (sender, e) =>
    Console.WriteLine(String.Format("Just downloaded {0} byte{1} at position {2}.",
            e.Length, e.Length == 1 ? "" : "s", e.Start));
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top