簡體   English   中英

使用 C# 異步編程中讀取多個大文件

[英]Read Multiple Big Files in Using C# Async Programming

我想讀取可能包含數百萬行的多個文件。

  1. 文件是多個我想同時多個文件。
  2. 每個文件包含數百萬行,因此如果按順序讀取行,則需要時間。 所以我想同時讀取多行。
  3. 在每一行讀取根據行中的值做一些處理。

我有幾個疑問。

問題:由於文件讀取是 I/O 操作,因此同時讀取多個文件將使用異步編程,我應該使用

  1. 簡單的異步/等待 model
  2. Task.Run(Read_File(filePath))

您不會通過同時讀取多個文件來節省時間,除非每個文件都位於不同的存儲硬件中。 從文件系統讀取文件的速度受限於文件所在的存儲硬件的能力,而不是 CPU 的能力。

為了節省時間,您可以做的是在不間斷的工作流程中讀取文件,並在不同的工作流程中處理行,這兩個工作流程同時獨立工作。 生產者工作流和消費者工作流這兩個工作流之間的通信可以使用具有阻塞或異步功能的中間緩沖區來實現。 有許多可用選項,包括BlockingCollection class、 ChannelsTPL Dataflow庫。

當你讀取一個文件,尤其是硬盤上的文件時,讀取速度相對較慢,因為設備必須等到正確的扇區位於讀取磁頭下方。 同時讀取多個文件,不會提高性能,因為讀取文件 A 的一個扇區后,讀頭必須移動讀取文件 B 的一個扇區,然后返回讀取文件 A 的下一個扇區。

所以同時讀取兩個文件是不明智的。

你想做的事,為生產者-消費者模式尖叫:生產者盡可能快地生產數據,而消費者以不同的速度處理數據:有時比生產者慢,有時比生產者更快。 尚未處理的數據必須被緩沖。

如果您使用生產者-消費者模式,則生產者讀取文件並將讀取的行盡可能快地放在緩沖區上。 每當文件讀取器必須等待下一批數據時,消費者就有一些時間來處理已經生成的行。

微軟為此提供了一個簡單的 Nuget package: Microsoft Task Parallel Library

首先你創建一個緩沖區。 所有讀取的行都將存儲在此緩沖區中:

private BufferBlock<string> buffer = new BufferBlock<string>();

讀取文件並將讀取的行存儲在緩沖區中的異步過程:

async Task ProduceLinesAsync(string fileName)
{
    using (TextReader fileReader = File.OpenText(fileName))
    {
        string readLine = await fileReader.ReadLineAsync();
        while (readLine != null)
        {
            // a line has been read; put it on the buffer:
            buffer.SendAsync(readLine);

            // read the next line                
            readLine = await fileReader.ReadLineAsync();
        }
    }
}

處理多個文件的過程:

async Task ProduceLinesAsync(IEnumerable<string> fileNames)
{
    foreach (var fileName in fileNames)
    {
        await ProduceLinesAsync(fileName);
    }

    // If here, nothing to produce anymore.
    // tell the buffer that producing is finished:
    buffer.Complete();
}

如果需要,您可以讓每個文件在不同的緩沖區上生成數據,並且每個緩沖區有一個使用者。

消費者

您會在生產時看到所有等待:每當進程等待讀取下一行時,消費者將有時間處理已經生產的行:

Task ConsumeAsync()
{
    while (await buffer.OutputAvailableAsync())
    {
        // there is something on the buffer; fetch it and process it:
        var line = await buffer.ReceiveAsync();
        this.ProcessLine(line);
    }

    // if here, producer marked Complete(), indicating that no data is to be expected
}

把它們放在一起:

async Task ProcessFiles(IEnumerable<string> fileNames)
{
    // start producing, but do not await:
    Task taskProduce = ProduceLinesAsync(fileNames);

    // because we did not await, we are free to do the following as soon as the
    // TextReader has to await for a line.
    // again, do not await.
    Task taskConsume = ConsumeAsync();

    // await until both the producer and the consumer are finished:
    await Task.WhenAll(new Task[] {taskProduce, taskConsume})
}

結論

通過在 async await 中使用 BufferBlock,只有當所有數據都處理完,並且下一行還沒有讀完時,進程才會空閑等待。

在所有其他情況下,每當您的進程必須等待 TextReader 生成下一行時,它將處理尚未處理的行。 當有工作要做時,您的流程永遠不會閑置。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM