C# 如何并行执行有序执行序列。对于?

C# 如何并行执行有序执行序列。对于?,c#,multithreading,.net-core,seq,concurrent-processing,C#,Multithreading,.net Core,Seq,Concurrent Processing,我有一个简单的并行循环做一些事情,然后我将结果保存到一个文件中 object[] items; // array with all items object[] resultArray = new object[numItems]; Parallel.For(0, numItems, (i) => { object res = doStuff(items[i], i); resultArray[i] = res; }); foreach (object res in

我有一个简单的并行循环做一些事情,然后我将结果保存到一个文件中

object[] items; // array with all items
object[] resultArray = new object[numItems];
Parallel.For(0, numItems, (i) => 
{ 
    object res = doStuff(items[i], i);
    resultArray[i] = res;
});

foreach (object res in resultArray)
{
    sequentiallySaveResult(res);
}
为了保存,我需要按正确的顺序写入结果。通过将结果放入
resultArray
,结果的顺序再次正确

然而,由于结果非常大,占用了大量内存。 我想按顺序处理这些项目,例如,四个线程开始处理项目1-4,下一个空闲线程处理项目5等等

这样,我就可以启动另一个线程,监视数组中下一个需要写入的项(或者当一个项完成时,每个线程都可以发出一个事件),这样我就可以在后面的项仍在处理时开始写入第一个结果,然后释放内存

是否可以并行。按给定顺序处理项目?我当然可以使用
concurentQueue
,将所有索引按正确的顺序放入其中,然后手动启动线程

但是如果可能的话,我想保留所有关于使用多少线程等的自动化,这些都是“Parallel.For”实现

免责声明:我无法切换到
ForEach
,我需要
I

编辑#1:
目前,执行顺序完全是随机的,例如:

Processing item 1/255
Processing item 63/255
Processing item 32/255
Processing item 125/255
Processing item 94/255
Processing item 156/255
Processing item 187/255
Processing item 249/255
...
编辑#2:
已完成作业的更多详细信息:

我处理一个灰度图像,需要提取每个“层”(上面例子中的项目)的信息,所以我从0到255(8位)对图像执行一项任务

我有一个类可以同时访问像素值:

 unsafe class UnsafeBitmap : IDisposable
    {

        private BitmapData bitmapData;
        private Bitmap gray;
        private int bytesPerPixel;
        private int heightInPixels;
        private int widthInBytes;
        private byte* ptrFirstPixel;

        public void PrepareGrayscaleBitmap(Bitmap bitmap, bool invert)
        {
            gray = MakeGrayscale(bitmap, invert);

            bitmapData = gray.LockBits(new Rectangle(0, 0, gray.Width, gray.Height), ImageLockMode.ReadOnly, gray.PixelFormat);
            bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(gray.PixelFormat) / 8;
            heightInPixels = bitmapData.Height;
            widthInBytes = bitmapData.Width * bytesPerPixel;
            ptrFirstPixel = (byte*)bitmapData.Scan0;
        }

        public byte GetPixelValue(int x, int y)
        {
            return (ptrFirstPixel + ((heightInPixels - y - 1) * bitmapData.Stride))[x * bytesPerPixel];
        }

        public void Dispose()
        {
            gray.UnlockBits(bitmapData);
        }
    }
循环是

UnsafeBitmap ubmp;//已初始化,具有正确的位图
int numLayers=255;
int bitmapWidthPx=10000;
int位图高度px=10000;
object[]resultArray=新对象[numLayer];
对于(0,numLayers,(i)=>
{ 
对于(int x=0;x
我还想启动一个保存线程,检查下一个需要写入的项目是否可用,写入,从内存中丢弃。对于这一点,如果处理按顺序开始就好了,这样结果就大致按顺序到达。如果第5层的结果排在倒数第二位,我必须等待写入第5层(以及所有后续内容)直到结束


如果4个线程开始,开始处理第1-4层,当一个线程完成后,开始处理第5层,下一个第6层,依此类推,结果或多或少会以相同的顺序出现,我可以开始将结果写入文件并从内存中丢弃它们。

如果您想对线程操作进行排序,线程同步101教我们使用条件变量,要在C#任务中实现这些变量,您可以使用提供异步等待函数的
SemaphoreSlim
。再加上一次计数器检查,你会得到想要的结果

但是,我不认为这是必要的,因为如果我理解正确,并且您只想按顺序保存它们以避免将它们存储在内存中,那么您可以使用内存映射文件:

  • 如果结果大小相同,只需在
    index*size
    位置写入缓冲区即可

  • 如果结果大小不同,则在获得结果时写入临时映射文件,并让另一个线程在结果出现时复制正确的顺序输出文件。这是一个IO绑定的操作,因此不要使用任务池


  • Parallel
    类知道如何并行化工作负载,但不知道如何合并处理的结果。因此,我建议改为使用。您要求以原始顺序保存结果并与处理同时进行,这使得它比通常情况下要复杂一些,但它仍然是完全可行的:

    IEnumerable<object> results = Partitioner
        .Create(items, EnumerablePartitionerOptions.NoBuffering)
        .AsParallel()
        .AsOrdered()
        .WithMergeOptions(ParallelMergeOptions.NotBuffered)
        .Select((item, index) => DoStuff(item, index))
        .AsEnumerable();
    
    foreach (object result in results)
    {
        SequentiallySaveResult(result);
    }
    
    IEnumerable results=Partitioner
    .Create(项,枚举分区选项.NoBuffering)
    .天冬酰胺()
    .AsOrdered()
    .WithMergeOptions(ParallelMergeOptions.NotBuffered)
    .选择((项目,索引)=>DoStuff(项目,索引))
    .AsEnumerable();
    foreach(结果中的对象结果)
    {
    顺序平均结果(结果);
    }
    
    说明:

  • 按原始顺序检索结果需要运算符
  • 操作员需要防止缓冲结果,以便在结果可用时立即保存
  • 是必需的,因为数据源是一个数组,而PLINQ默认情况下静态地划分数组。这意味着数组被划分为多个范围,并为每个范围分配一个线程。这通常是一个很好的性能优化,但在这种情况下,它无法及时有序地检索结果。因此需要一个动态分区器,从开始到结束依次枚举源
  • 该配置可防止PLINQ使用的工作线程一次捕获多个项目(这是默认的PLINQ分区技巧,称为“块分区”)
  • 实际上并不需要
    AsEnumerable
    。它只是为了表示并行处理的结束。接下来的
    foreach
    ParallelQuery
    视为
    IEnumerable
  • 由于需要所有这些技巧,并且由于此解决方案在以后需要在处理管道中添加更多并发异构步骤时不够灵活