多线程文件处理

时间:2013-07-12 17:30:53

标签: c# multithreading

我想优化此代码:

    public static void ProcessTo(this StreamReader sr, StreamWriter sw, Action<StreamWriter, string> action, FileProcessOptions fpo = null)
    {
        if (fpo == null)
        {
            fpo = new FileProcessOptions();
        }

        List<string> buffer = new List<string>(fpo.BuferSize);

        while (!sr.EndOfStream)
        {
            buffer.Clear();

            while (!sr.EndOfStream && buffer.Count < fpo.BuferSize)
            {
                buffer.Add(sr.ReadLine());
            }

            if (fpo.UseThreads)
            {
                buffer.AsParallel().ForAll(line => action(sw, line));
            }
            else
            {
                buffer.ForEach(line => action(sw, line));
            }
        }
    }

我处理大量数据并希望并行化流程。 通常数据存档,因此使用多个线程处理数据流非常重要

2 个答案:

答案 0 :(得分:2)

如果你没有传递StreamReader,而只是传递文件名,你可以写:

Parallel.Foreach(File.ReadLines(filename), (line) => action(sw, line));

如果您传递StreamReader,仍然可以执行此操作。您只需创建一个将读取它的枚举器。像这里所做的一样:Recommended behaviour of GetEnumerator() when implementing IEnumerable<T> and IEnumerator<T>。使用它,你会写:

LineReaderEnumerable myEnumerable = new LineEnumerator(sr);
Parallel.Foreach(myEnumerable, (line) => action(sw, line));

但是,你有一个潜在的问题,因为你可以有多个线程写入该流编写器。 StreamWriter不支持并发写入。它会引发异常。如果您正在同步对输出文件的访问(例如,使用锁定),那么您可以在这里。

您将遇到的另一个问题是输出内容的顺序。几乎可以肯定的是,如果按照[1, 2, 3, 4, ... n]的顺序读取行,输出顺序将会不同。你可能得到[1, 2, 4, 3, 6, 5, 7, 9, 8 ... n, n-1]。如果输出顺序很重要,你必须想出办法确保以正确的顺序输出内容。

关于锁定,你有:

sr.ProcessParalel(line => 
{ 
    string[] ls = line.Split('\t');
    lock (sw)
    {
        sw.Write(float.Parse(ls[0]));
        sw.Write(int.Parse(ls[1]) * 10 + 1);
        for (int i = 2; i < ls.Length; i++)
        {
            sw.Write(int.Parse(ls[1]));
        }
    }
 });

问题不在于锁定。问题是你持有锁太久了。编写它的方式,代码实际上是单线程的,因为所有线程都在等待该锁来进行处理。您需要更改处理,以便尽可能短的时间保持锁定。

将输出构建为StringBuilder,将其转换为字符串,然后输出该字符串。例如:

string[] ls = line.Split('\t');
StringBuilder sb = new StringBuilder();
sb.Append(float.Parse(ls[0]));
sb.Append(' ');
sb.Append(int.Parse(ls[1])) * 10 + 1);
for (int i = 2; i < ls.Length; i++)
{
    sb.Append(' ');
    sb.Append(int.Parse(ls[i]));    }
}
var sout = sb.ToString();
// lock and write
lock (sw)
{
    sw.Write(sout);
}

你可以用StringWriter做同样的事情。

答案 1 :(得分:0)

最终解决方案:

        public static IEnumerable<string> GetEnumirator(this StreamReader sr)
    {
        while (!sr.EndOfStream)
        {
            yield return sr.ReadLine();
        }
    }

    public static void ProcessParalel(this StreamReader sr, Action<string> action)
    {
        sr.GetEnumirator().AsParallel().ForAll(action);
    }

    public static void ProcessTo(this StreamReader sr, BinaryWriter bw, Action<BinaryWriter, string> action, FileProcessOptions fpo = null)
    {
        sr.ProcessParalel(line =>
        {
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryWriter lbw = new BinaryWriter(ms);

                action(lbw, line);
                ms.Seek(0, SeekOrigin.Begin);

                lock (bw)
                {
                    ms.WriteTo(bw.BaseStream);
                }
            }
        });
    }

使用压缩输入流,我得到加速3次