iLe*_*ing 2 parallel-processing multithreading task task-parallel-library c#-4.0
抱歉,代码量很大,我无法用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();
}
}
Run Code Online (Sandbox Code Playgroud)
这不是 中的错误StringBuilder,而是您代码中的错误。并且您在后续答案中显示的修改(您Log.String用一次提取一个字符的循环替换)并没有修复它。它不会再抛出异常,但也无法正常工作。
问题是您StringBuilder在多线程代码中的两个地方使用了,其中一个没有尝试锁定它,这意味着读取可以在一个线程上同时发生在另一个线程上。特别是,问题是这一行:
_log.Log.AppendLine(act.Invoke());
Run Code Online (Sandbox Code Playgroud)
你在你的Parallel.ForEach. 您没有在此处进行任何同步尝试,即使这将一次在多个线程上运行。所以你有两个问题:
AppendLine可能在多个线程上同时进行多个调用Log.ToString在一个或多个其他线程正在调用的同时进行调用AppendLine您一次只能读取一次,因为您正在使用lock关键字来同步它们。问题是您在调用AppendLine.
你的“修复”并不是真正的修复。你成功的只是让问题更难被发现。它现在只会以不同和更微妙的方式出错。例如,我假设您的Write方法Log.Clear在您的for循环完成其最终迭代后仍会继续调用。在完成最后一次迭代和调用 之间Log.Clear,其他线程可能会进入另一个调用,AppendLine因为这些调用没有同步AppendLine。
结果是你有时会错过一些东西。代码会将内容写入字符串构建器,然后在不写入流写入器的情况下将其清除。
此外,并发AppendLine调用很有可能导致问题。如果幸运的话,它们会不时崩溃。(这很好,因为它清楚地表明你有一个问题需要解决。)如果你不走运,你会不时地得到数据损坏 - 两个线程最终可能会写入同一个地方,StringBuilder结果要么是混乱,或完全丢失数据。
同样,这不是StringBuilder. 它不支持从多个线程同时使用。您的工作是确保一次只有一个线程对StringBuilder. 正如该类的文档所说,“不保证任何实例成员都是线程安全的。”
显然,您不想在调用 act.Invoke() 时保持锁定,因为这可能正是您想要并行化的工作。所以我猜这样的事情可能会更好:
string result = act();
lock(_log.Log)
{
_log.Log.AppendLine(result);
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我把它留在那里,我真的不会帮助你,因为这对我来说看起来很不对。
如果您发现自己锁定了其他人对象中的字段,则表明您的代码存在设计问题。修改设计可能更有意义,以便该LogBuilder.Write方法接受字符串。老实说,我什至不知道你为什么在StringBuilder这里使用 a ,因为你似乎把它用作一个字符串的保存区域,你立即写入流编写器。你希望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();
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3972 次 |
| 最近记录: |