Parallel For Loop

Nov 25, 2014 at 4:59 AM
Edited Nov 25, 2014 at 5:02 AM
Hello everyone,

I'm having an issue when I try to run a parallel for loop. I'm not quite sure how to access API objects inside of the Parallel.For. When I run the following code I get Exception Code: 0x0000094 "the thread tried to divide an integer value by an integer divisor of zero".

How do I access the same dose object from multiple threads?

In the below example I created zDosePlaneList, a list of Dose Arrays to store the [x,y] voxels for every z slice.

int[,] dosePlaneVoxelsOptimized = new int[planDose.XSize, planDose.YSize];
planDose.GetVoxels( z, dosePlaneVoxelsOptimized );

I would like to just include this in the parallel for loop but the call to planDose.GetVoxels() is causing the exception to be thrown. In the below code, threadLocalDose.Value.VoxelToDoseValue() is causing the exception to be thrown.
            count = 0;
            int[,] dosePlaneVoxelsOptimized = new int[planDose.XSize, planDose.YSize];
            double doseToCompare = doseToCompare.Dose;

            try
            {
                var exceptions = new ConcurrentQueue<Exception>();
                ThreadLocal<Dose> threadLocalDose = 
                                                new ThreadLocal<Dose>( () => planItem.Dose);
              
                Parallel.For( 0, planDose.ZSize, () => 0, ( z, loop, subcount ) =>
                {
                    for ( int x = 0; x < dosePlaneVoxelsOptimized.GetLength( 0 ); x++  )
                    {
                        for ( int y = 0; y < dosePlaneVoxelsOptimized.GetLength( 1 ); y++  )
                        {
                            try
                            {
                                double voxelDose = 
                                      threadLocalDose.Value.VoxelToDoseValue(
                                                 zDosePlaneList.ElementAt( z )[x, y] ).Dose;
                                if ( voxelDose >= doseToCompare)
                                {
                                subcount++ ;
                                } 
                            }
                            catch ( Exception e ) { exceptions.Enqueue( e ); }
                        } 
                    } 
                    return subcount;
                },
                ( finalCount ) => Interlocked.Add( ref count, finalCount )
                ); // End for loop to cycle through z dose planes with Parallel For
            }
            catch ( AggregateException ae )
            {
                MessageBox.Show( "Exception: "  + ae.InnerException.ToString() );
            }
I get the same exception even when I create a custom STA Task Scheduler (as described in STA Task Scheduler and set the parallel loop option's Task Scheduler to this custom STA task Scheduler.
            STATaskScheduler staTaskScheduler = new DVH.STATaskScheduler( 2 );
            var parallelForOptions = new ParallelOptions();   
            parallelForOptions.TaskScheduler = staTaskScheduler;
Thanks,
Kurt
Nov 25, 2014 at 2:22 PM
Hello Kurt,

Unfortunately the Eclipse API doesn't support threading, all the API calls have to be in the main thread.

In some cases there are ways to work around this limitation, look for some post by Rex explaining his approach.

Hope this helps,
Eduardo
Coordinator
Nov 25, 2014 at 3:13 PM
Edited Nov 25, 2014 at 3:22 PM
I think the key here is to separate ESAPI data calls from the parallel computations. What about creating the dose structure first before doing any computations on it? Something like:
//DO DATA ACCESS
var plan = pat.Courses.First().PlanSetups.First();
var doseCube = new List<double[,]>();
for (int z = 0; z < plan.Dose.ZSize; z++)
{
    var dosePlane = new double[plan.Dose.XSize, plan.Dose.YSize];
    var doseBuffer = new int[plan.Dose.XSize, plan.Dose.YSize];
    plan.Dose.GetVoxels(0, doseBuffer);
    for (int x = 0; x < dosePlane.GetLength(0); x++)
    {
        for (int y = 0; y < dosePlane.GetLength(1); y++)
        {
            dosePlane[x, y] = plan.Dose.VoxelToDoseValue(doseBuffer[x, y]).Dose;
        }
    }
    doseCube.Add(dosePlane);
}
//DO COMPUTATIONS
double meanDose = 0.0;
Parallel.For(0, doseCube.Count, (i) =>
{
    var dosePlane = doseCube[i];
    for (int x = 0; x < dosePlane.GetLength(0); x++)
    {
        for (int y = 0; y < dosePlane.GetLength(1); y++)
        {
            meanDose += dosePlane[x, y];
        }
    }
});
meanDose /= plan.Dose.XSize * plan.Dose.YSize * plan.Dose.ZSize;
Marked as answer by rexcardan on 11/25/2014 at 8:00 AM
Nov 26, 2014 at 3:11 AM
Thanks Eduardo and Rex

It's interesting on a plan with 1mm dose grid and ~27,000,000 points it takes ~20sec to loop through them sequentially and ~0.15sec in parallel (suppose this would vary by hardware though). If multiple computations are to be done with the dose, it would be useful. On the same plan with 2.5mm grid it probably wouldn't be noticed as it only takes ~1.3sec to loop through sequentially, which makes sense given the reduction in points.

-Kurt