【问题标题】:Process and delete lines from file, stop if CancellationToken is send处理和删除文件中的行,如果发送 CancellationToken 则停止
【发布时间】:2021-08-13 07:06:01
【问题描述】:

我必须处理包含路径和状态信息的文件行,并为该行/路径启动上传操作。在某些情况下上传可能失败,所以我必须保留该行,在其他情况下,可以在上传成功时删除该行。主要问题是,一切都在后台运行,用户可以随时关闭软件。在这种情况下,我设置了一个 Cancellation-token 并且文件操作完成。 到现在为止,文件总是很小。所以我将所有仍然需要的行复制到一个新文件中并替换了旧文件。 简化代码:

bool valid=false;
bool cancelled=false;
using (StreamWriter sw = new StreamWriter(filename_tmp, false))
{
    try
    {
        using (StreamReader sr = new StreamReader(filename)) //alternative:
            // foreach (string line in File.ReadLines(filename))
        {
            // process each line of file
            while (sr.Peek() >= 0)
            {
                string line = sr.ReadLine();
                rowCnt++;
                //separate line into content:
                string[] content = line.Split(delimiter);
                if (canceltoken.IsCancellationRequested && (rowCnt > 3))
                {
                    cancelled = true;
                }
                else
                {
                    data_path = content[0];
                    //start upload:
                    valid = UploadData(data_path);
                }
                if( cancelled || valid==false)
                {
                     sw.WriteLine("{0},{1},{2},{3}", data_path, uploadCnt,
                         DateTime.Now.ToString(), errorMsg);
                }
            }
        }
    }       
}
File.Replace(filename_tmp, filename, filename_backup);  

现在我们遇到文件可能变得非常大的情况,我担心将所有内容复制到新文件中会花费太长时间。用户当前收到一条消息,表明仍有进程在运行,软件将在之后关闭。 1-5 秒后,软件关闭。现在需要更长的时间,我不希望用户使用任务管理器来终止进程。 处理行并在之后删除它的最佳方法是什么?我对文件有完全的控制权,因为我写了它。所以我可以自己定义格式和编写器(例如StreamWriter vs. BinaryWriter)。

我想到了两种可能的选择:

  1. 处理整个文件。为每一行设置一个状态标志(如 1=删除我,2=必须处理/保留)。处理完文件后,再次遍历行并复制所需的行。如果取消,请保留旧文件。

我想做这样的事情:

var linesToKeep = File.ReadLines(fileName).Where(l => l.Contains("remove me") ==false);
File.WriteAllLines(tempFile, linesToKeep);

但这需要我写到同一行来更改状态。我不确定那是否有效。我可以使用 BinaryWriter 来覆盖“标志”,但是我不能使用上面的行并且需要再次遍历每一行。

  1. 使用 seek 从末尾处理文件。如果我使用BinaryWriter,我会确切地知道行的长度,所以这不是问题。在额外文件中写入需要再次处理的错误行。使用FileStream.SetLength 在最后处理的行“剪切”原始文件。这将产生 2 个文件(原始文件包含未处理的行,第二个文件包含需要再次处理的行)。但我还不知道如何处理额外的文件。我可以在下次开始时先处理这个文件,但随后我可能会得到越来越多的文件,这似乎是错误的。

我不知何故被困在这里,我不知道如何进一步进行。任何提示将不胜感激。

【问题讨论】:

  • 与其把还没上传的每一行都记录下来,不如只记录最后上传成功的那一行?然后如果程序重新启动,则从该行继续。
  • 问题是,我会在两者之间松散线。可能是我上传4行成功,然后后面两行有问题,后来又上传了100行成功。如何跟踪出现错误的 2 行?
  • 出错的行仍会记录到不同的文件中。
  • 处理完主文件中的所有行后,将处理错误文件中的行。

标签: c# winforms filestream cancellation-token binaryreader


【解决方案1】:

我不会在取消后写入(或标记)所有未处理的行,而是反转问题:定义所有作业(每行一个),将其写入“队列”并在上传完成后将其删除。

要保留您的队列,您可以使用LiteDb。它是一个小巧方便的 No​​SQL 文件数据库,因此您没有 OR Mapper 的开销。

逻辑可能如下所示:

  1. 使用您的内容和一些附加属性(URL、...)定义一个 UploadJob 类
  2. 将作业列表写入 LiteDB 集合。
  3. 迭代每个作业。
  4. 作业成功完成后,在 LiteDB 中删除此项目。

【讨论】:

  • 在这种情况下,我可以直接在本地数据库中工作,不需要任何文件操作。是对的吗?我还需要排队吗?我总是担心数据库可能会崩溃并需要修复。但这是我完全忘记考虑的方式;-)。
  • 队列实际上是您的数据库集合(它像队列一样工作)。您可以使用一个语句 (coll.Delete(id)) 删除一个项目。如果您以共享模式打开数据库(请参阅litedb.org/docs/connection-string),引擎会在每次操作后关闭数据库文件。我在 Azure 应用服务的生产环境中使用这个数据库,它不是最快的,但非常健壮。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-01
  • 2012-05-13
  • 1970-01-01
  • 2021-02-11
  • 1970-01-01
  • 2013-02-25
相关资源
最近更新 更多