gpt4 book ai didi

multithreading - Streamwriter、StringBuilder 和并行循环

转载 作者:行者123 更新时间:2023-12-04 08:26:28 27 4
gpt4 key购买 nike

抱歉,代码太多,我无法用 less 来解释它。基本上我正在尝试从许多任务中写入一个文件。你们能告诉我我做错了什么吗? _streamWriter.WriteLine() 抛出 ArgumentOutOfRangeException

class Program
{
private static LogBuilder _log = new LogBuilder();
static void Main(string[] args)
{
var acts = new List<Func<string>>();
var rnd = new Random();
for (int i = 0; i < 10000; i++)
{
acts.Add(() =>
{
var delay = rnd.Next(300);
Thread.Sleep(delay);
return "act that that lasted "+delay;
});
}

Parallel.ForEach(acts, act =>
{
_log.Log.AppendLine(act.Invoke());
_log.Write();
});
}
}

public class LogBuilder : IDisposable
{
public StringBuilder Log = new StringBuilder();
private FileStream _fileStream;
private StreamWriter _streamWriter;

public LogBuilder()
{
_fileStream = new FileStream("log.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
_streamWriter = new StreamWriter(_fileStream) { AutoFlush = true };
}
public void Write()
{
lock (Log)
{
if (Log.Length <= 0) return;
_streamWriter.WriteLine(Log.ToString()); //throws here. Although Log.Length is greater than zero
Log.Clear();
}
}

public void Dispose()
{
_streamWriter.Close(); _streamWriter.Dispose(); _fileStream.Close(); fileStream.Dispose();
}
}

最佳答案

这不是 StringBuilder 中的错误,而是您代码中的错误。并且您在后续答案中显示的修改(您将 Log.String 替换为一次提取一个字符的循环)并没有解决它。它不会再抛出异常,但也无法正常工作。

问题是您在多线程代码中的两个地方使用了 StringBuilder,其中一个没有尝试锁定它,这意味着读取可以在写入的同时在一个线程上发生发生在另一个。特别是,问题是这一行:

_log.Log.AppendLine(act.Invoke()); 

您在 Parallel.ForEach 中执行此操作。您没有在此处进行任何同步尝试,即使这将同时在多个线程上运行。所以你有两个问题:

  1. AppendLine 的多个调用可能在多个线程上同时进行
  2. 一个线程可能会尝试在一个或多个其他线程调用 AppendLine 的同时调用 Log.ToString

您一次只能读取一个,因为您正在使用 lock 关键字来同步它们。问题是您在调用 AppendLine 时也没有获得相同的锁。

您的“修复”并不是真正的修复。你只是成功地让问题更难被发现。它现在只会以不同的和更微妙的方式出错。例如,我假设您的 Write 方法在您的 for 循环完成其最后一次迭代后仍继续调用 Log.Clear。在完成最后一次迭代和调用 Log.Clear 之间,其他线程可能会进入对 AppendLine 的另一个调用,因为没有同步那些对 AppendLine 的调用。

结果是您有时会错过一些东西。代码会将内容写入字符串构建器,然后在不写入流编写器的情况下将其清除。

此外,并发 AppendLine 调用很有可能会导致问题。如果你幸运的话,他们会不时崩溃。 (这很好,因为它清楚地表明您有问题需要解决。)如果您不走运,您会不时遇到数据损坏 - 两个线程可能最终写入 StringBuilder 中的同一个位置 导致数据困惑或完全丢失。

同样,这不是 StringBuilder 中的错误。它不支持从多个线程同时使用。您的工作是确保一次只有一个线程对 StringBuilder 的任何特定实例执行任何操作。正如该类的文档所说,“不保证任何实例成员都是线程安全的。”

显然,您不想在调用 act.Invoke() 时持有锁,因为这可能正是您想要并行化的工作。所以我猜这样的事情可能会更好:

string result = act();
lock(_log.Log)
{
_log.Log.AppendLine(result);
}

但是,如果我把它留在那里,我就不会真正帮助你,因为这对我来说看起来很不对。

如果您发现自己锁定了其他人的对象中的字段,则表明您的代码存在设计问题。修改设计可能更有意义,以便 LogBuilder.Write 方法接受一个字符串。老实说,我什至不确定你为什么要在这里使用 StringBuilder,因为你似乎只是将它用作你立即写入流编写器的字符串的保存区域.您希望 StringBuilder 在这里添加什么?以下会更简单并且似乎不会丢失任何东西(除了原始的并发错误):

public class LogBuilder : IDisposable
{
private readonly object _lock = new object();
private FileStream _fileStream;
private StreamWriter _streamWriter;

public LogBuilder()
{
_fileStream = new FileStream("log.txt", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
_streamWriter = new StreamWriter(_fileStream) { AutoFlush = true };
}
public void Write(string logLine)
{
lock (_lock)
{
_streamWriter.WriteLine(logLine);
}
}

public void Dispose()
{
_streamWriter.Dispose(); fileStream.Dispose();
}
}

关于multithreading - Streamwriter、StringBuilder 和并行循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9331648/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com