Parallel.ForEach 漂亮的记录不可能?
Parallel.ForEach Pretty Logging Impossible?
我认为使用 Parallel.ForEach 时不可能有漂亮的日志。除非有人有一些技巧,他们可以告诉我吗?
namespace Parallel_ForEach_Logging
{
class Program
{
private static uint _callCount = 0;
static void Main(string[] args)
{
IEnumerable<int> data = Enumerable.Range(0, 2);
Console.Out.WriteLine("***");
ParallelOptions options = new();
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
Parallel.ForEach(data, options, datum =>
{
PrintNum(datum, _callCount++);
});
Console.Out.Flush();
Console.Out.WriteLine("***");
}
public static void PrintNum(int num, uint callCount)
{
Console.Out.WriteLine($">>> IN {callCount}");
// Console.Out.WriteLine($" {num}");
Console.Out.WriteLine($"<<< OUT {callCount}");
PrintString($"\"{num.ToString()}\"", callCount);
}
public static void PrintString(string str, uint callCount)
{
Console.Out.WriteLine($">>> IN {callCount}");
// Console.Out.WriteLine($" {str}");
Console.Out.WriteLine($"<<< OUT {callCount}");
}
}
}
当我说漂亮的日志时,我想到的是:
>>> IN: Method A
>>> IN : Method 1 called.
<<< OUT: Method 1 done.
>>> IN : Method 2 called.
<<< OUT: Method 2 done.
<<< OUT: Method A done in 32 milliseconds.
>>> IN: Method B
...
这是我目前收到的示例日志:
>>> IN 1
>>> IN 0
<<< OUT 0
<<< OUT 1
>>> IN 0
<<< OUT 0
>>> IN 1
<<< OUT 1
注意顶部有两个 IN 背靠背,然后两个 OUT 背靠背。
在代码 _callCount 中,我尝试计算用于缩进的调用深度,但我不知道如何进行这项工作。即使我这样做了,如果每个日志条目中没有一百万个空格,我也不确定我能否做到。
我知道如果我把 Parallel.ForEach 排除在等式之外,我肯定可以完成这项工作。
如果这真的是不可能完成的,我也没关系。我只是需要一个比我聪明得多的人来这么说。 ;)
您可以锁定对 PrintString 的调用。请记住,这样做会停止您的其他线程,直到当前线程释放锁。所以在这种情况下,通过这样做,您的代码实际上是顺序的,但是线程打印的顺序实际上是随机的。
除了锁定线程之外,我能想到的唯一其他方法是使用 ConcurrentBag 在元组或 class 中包含调用计数和方法名称。然后在 ForEach 完成后,您可以使用调用计数和方法名称对该集合进行排序,以便它以顺序方式显示。
这可能是作弊,因为它不是实时的,但除此之外我不知道你如何实现这一点。
使用 StringBuilder 作为缓冲区。将它作为参数添加到您的打印函数中,如下所示:
public static void PrintNum(StringBuilder builder, int num, uint callCount)
{
builder.Append($">>> IN {callCount}\r\n");
builder.Append($"<<< OUT {callCount}\r\n");
PrintString(builder, $"\"{num.ToString()}\"", callCount);
}
public static void PrintString(StringBuilder builder, string str, uint callCount)
{
builder.Append($">>> IN {callCount}\r\n");
builder.Append($"<<< OUT {callCount}\r\n");
}
现在您可以这样做了:
Parallel.ForEach(data, options, datum =>
{
var builder = new StringBuilder();
PrintNum(builder, datum, _callCount++);
Console.Out.WriteLine(builder.ToString());
});
使用 PLinq,您可以实现:
var query = data.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.Select(datum => Task.Run(() => PrintNum(datum, _callCount++)));
await Task.WhenAll(query);
以下输出:
***
>>> IN 0
<<< OUT 0
>>> IN 1
<<< OUT 1
>>> IN 0
<<< OUT 0
>>> IN 1
<<< OUT 1
***
我认为使用 Parallel.ForEach 时不可能有漂亮的日志。除非有人有一些技巧,他们可以告诉我吗?
namespace Parallel_ForEach_Logging
{
class Program
{
private static uint _callCount = 0;
static void Main(string[] args)
{
IEnumerable<int> data = Enumerable.Range(0, 2);
Console.Out.WriteLine("***");
ParallelOptions options = new();
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
Parallel.ForEach(data, options, datum =>
{
PrintNum(datum, _callCount++);
});
Console.Out.Flush();
Console.Out.WriteLine("***");
}
public static void PrintNum(int num, uint callCount)
{
Console.Out.WriteLine($">>> IN {callCount}");
// Console.Out.WriteLine($" {num}");
Console.Out.WriteLine($"<<< OUT {callCount}");
PrintString($"\"{num.ToString()}\"", callCount);
}
public static void PrintString(string str, uint callCount)
{
Console.Out.WriteLine($">>> IN {callCount}");
// Console.Out.WriteLine($" {str}");
Console.Out.WriteLine($"<<< OUT {callCount}");
}
}
}
当我说漂亮的日志时,我想到的是:
>>> IN: Method A
>>> IN : Method 1 called.
<<< OUT: Method 1 done.
>>> IN : Method 2 called.
<<< OUT: Method 2 done.
<<< OUT: Method A done in 32 milliseconds.
>>> IN: Method B
...
这是我目前收到的示例日志:
>>> IN 1
>>> IN 0
<<< OUT 0
<<< OUT 1
>>> IN 0
<<< OUT 0
>>> IN 1
<<< OUT 1
注意顶部有两个 IN 背靠背,然后两个 OUT 背靠背。
在代码 _callCount 中,我尝试计算用于缩进的调用深度,但我不知道如何进行这项工作。即使我这样做了,如果每个日志条目中没有一百万个空格,我也不确定我能否做到。
我知道如果我把 Parallel.ForEach 排除在等式之外,我肯定可以完成这项工作。
如果这真的是不可能完成的,我也没关系。我只是需要一个比我聪明得多的人来这么说。 ;)
您可以锁定对 PrintString 的调用。请记住,这样做会停止您的其他线程,直到当前线程释放锁。所以在这种情况下,通过这样做,您的代码实际上是顺序的,但是线程打印的顺序实际上是随机的。
除了锁定线程之外,我能想到的唯一其他方法是使用 ConcurrentBag 在元组或 class 中包含调用计数和方法名称。然后在 ForEach 完成后,您可以使用调用计数和方法名称对该集合进行排序,以便它以顺序方式显示。
这可能是作弊,因为它不是实时的,但除此之外我不知道你如何实现这一点。
使用 StringBuilder 作为缓冲区。将它作为参数添加到您的打印函数中,如下所示:
public static void PrintNum(StringBuilder builder, int num, uint callCount)
{
builder.Append($">>> IN {callCount}\r\n");
builder.Append($"<<< OUT {callCount}\r\n");
PrintString(builder, $"\"{num.ToString()}\"", callCount);
}
public static void PrintString(StringBuilder builder, string str, uint callCount)
{
builder.Append($">>> IN {callCount}\r\n");
builder.Append($"<<< OUT {callCount}\r\n");
}
现在您可以这样做了:
Parallel.ForEach(data, options, datum =>
{
var builder = new StringBuilder();
PrintNum(builder, datum, _callCount++);
Console.Out.WriteLine(builder.ToString());
});
使用 PLinq,您可以实现:
var query = data.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.Select(datum => Task.Run(() => PrintNum(datum, _callCount++)));
await Task.WhenAll(query);
以下输出:
***
>>> IN 0
<<< OUT 0
>>> IN 1
<<< OUT 1
>>> IN 0
<<< OUT 0
>>> IN 1
<<< OUT 1
***