System.Threading.Tasks.Parallel error in c#

hi everyone
can help to this error when used System.Threading.Tasks.Parallel.For Loop in this code?

using System.IO;
using System.Linq;
using System.Threading.Tasks;
private void RunScript(List<Curve> curve, ref object A)
  {
       System.Threading.Tasks.Parallel.For(0, curve.Count, i =>
    // System.Threading.Tasks.Parallel.ForEach(curve, i =>
      {
      for (int j = curve.Count - 1;j > i; j--){
        var b = Curve.GeometryEquals(curve[i], curve[j]);
        if(b)
          curve.RemoveAt(j);
      }
      });
    A = curve;
  }

System.Threading.Tasks.Parallel.gh (125.3 KB)
@Mahdiyar

Think of your list of curves as a deck of cards. You and your friends are trying to remove any duplicate cards from the deck.

In your original code, you all start removing cards at the same time. But this causes a problem. Imagine if you’re looking at a card and at the same time, one of your friends removes a card from the deck. Now, the deck has changed and you might lose track of which card you were looking at. This is why your original code was giving an error. I did not check wether it will work in terms of logic or not, but I doubt It will.

Anyway to solve this specific threading issue you must use ConcurrentBag.

private void RunScript(List<Curve> curve, ref object A)
{
    var distinctCurves = new ConcurrentBag<Curve>();

    System.Threading.Tasks.Parallel.ForEach(curve, currentCurve =>
    {
        if (!distinctCurves.Any(c => Curve.GeometryEquals(c, currentCurve)))
        {
            distinctCurves.Add(currentCurve);
        }
    });

    A = distinctCurves.ToList();
}

Keep in mind this code is still not thread safe the Any method and the Add method are two separate operations. During parallel processing it’s possible for another thread to add an equivalent curve to distinctCurves after the Any check but before the Add method is called.
This could result in duplicate curves in distinctCurves .

Generally I avoid parallel computing in Rhino unless It is absolutely necessary, which for me means never.

1 Like

why use ConcurrentBag?

using System.Collections.Concurrent;
Did you add this directive?

1 Like

Yes, thanks, it works, but the result is wrong in Parallel Loop!
431 != 354


System.Threading.Tasks.Parallel.gh (227.0 KB)

" Keep in mind this code is still not thread safe the Any method and the Add method are two separate operations. During parallel processing it’s possible for another thread to add an equivalent curve to distinctCurves after the Any check but before the Add method is called.
This could result in duplicate curves in distinctCurves "

1 Like

@Rh-3d-p try combining the two algorithms. The regular for loop can clean up any the concurrent bag missed due to threading.

  private void RunScript(List<Curve> curve, ref object A)
  {
    Component.Name = "RemoveDuplicateCurves Parallel";
    string st = curve.Count.ToString();

    var distinctCurves = new ConcurrentBag<Curve>();

    System.Threading.Tasks.Parallel.ForEach(curve, currentCurve =>
      {
      if (!distinctCurves.Any(c => Curve.GeometryEquals(c, currentCurve)))
      {
        distinctCurves.Add(currentCurve);
      }
      });


    var dCurves = distinctCurves.ToList();

    for (int i = 0; i < dCurves.Count; i++)
    {
      for (int j = dCurves.Count - 1; j > i; j--)
      {
        var b = Curve.GeometryEquals(dCurves[i], dCurves[j]);
        if(b)
          dCurves.RemoveAt(j);
      }
    }


    A = dCurves;



  }

–Edit–
Of course, if your curves are going to be Line-like, use Kangaroo’s removeDuplicateLines
image

-Brian

1 Like

Thanks @Brian_Washburn and @Brian_Washburn
its useful i try to find best and faster way to remove duplicate curves and if enyone better suggestions share it
And i use this method that forLine is ok (and very fast)but for Curves not work:
This:
For Line this code is faster than Kangaroo components for remove dup Line but befor we must flip all normal Line to a guide Vector


using System.Collections.Generic;
using System.Linq;

// Define a custom equality comparer for Line objects
public class LineEqualityComparer : IEqualityComparer<Line>
{
    public bool Equals(Line x, Line y)
    {
        // Compare the start and end points of the lines
        return x.From.Equals(y.From) && x.To.Equals(y.To);
    }

    public int GetHashCode(Line obj)
    {
        // Generate a hash code based on the start and end points
        return obj.From.GetHashCode() ^ obj.To.GetHashCode();
    }
}

// Remove duplicate lines from the list
List<Line> uniqueLines = lines.Distinct(new LineEqualityComparer()).ToList();

And this Code for Curves don’t result!
And please suggestions for this code to work!

using System.Collections.Generic;
using Rhino.Geometry;
using System.Collections.Generic;
using Rhino.Geometry;

// Input list of curves
List<Curve> curveList = new List<Curve>(); // Add your list of curves here

// Create a HashSet to store unique curves
HashSet<Curve> uniqueCurves = new HashSet<Curve>();

// Iterate through the curve list and add unique curves to the HashSet
for (int i = 0; i < curveList.Count; i++)
{
    Curve currentCurve = curveList[i];

    // Check if the curve is unique
    if (!uniqueCurves.Contains(currentCurve))
    {
        // Add the curve to the HashSet
        uniqueCurves.Add(currentCurve);
    }
}

// Convert the HashSet back to a list of curves
List<Curve> uniqueCurveList = new List<Curve>(uniqueCurves);

Remove duplicate Curve

Hi @Rh-3d-p
I wasn’t familiar with HashSets prior to this post, but I asked ChatGPT for some help and got a functional result. It happens to be very close to the code posted by @RadovanG here:

The initial code by ChatGPT is this:

HashSet Check
private void RunScript(List<Curve> curves, ref object A)
  {
    A = CurveUtility.RemoveDuplicateCurves(curves);
  }

  // <Custom additional code> 
  public static class CurveUtility
  {
    public static List<Curve> RemoveDuplicateCurves(List<Curve> curves)
    {
      var uniqueCurves = new List<Curve>();
      var hashSet = new HashSet<uint>();
      foreach (var curve in curves)
      {
        // Reverse curve direction where necessary
        if (curve.PointAtStart > curve.PointAtEnd)
          curve.Reverse();

        var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
        if (!hashSet.Contains(hash))
        {
          hashSet.Add(hash);
          uniqueCurves.Add(curve);
        }
      }

      return uniqueCurves;
    }

    private static uint CreateCurveHashCodeX3(NurbsCurve crv, int salt)
    {
      int myHash = 0;
      unchecked
      {
        ///Comment or uncomment myHash for varing levels of duplication check
        for (int i = 0; i < crv.Points.Count; i++)
        {
          myHash ^= saltHash(crv.Points[i].Location.GetHashCode(), salt) * (31 * i + 1);
          myHash ^= saltHash(crv.Points[i].Weight.GetHashCode(), salt) * (97 * i + 1);
        }

        for (int i = 0; i < crv.Knots.Count; i++)
        {
          //myHash ^= crv.Knots[i].GetHashCode() * (2 * i + salt);
        }

        myHash ^= crv.Degree * 1559;
        myHash ^= crv.Dimension * 2029;
        //myHash ^= saltHash(crv.Domain.GetHashCode(), salt) * 1699;
        myHash ^= saltHash(crv.IsClosed.GetHashCode(), salt) * 4099;
        myHash ^= saltHash(crv.IsPeriodic.GetHashCode(), salt) * 4447;
        myHash ^= saltHash(crv.IsValid.GetHashCode(), salt) * 5557;
        myHash ^= crv.SpanCount * 9199;
      }
      return (uint) myHash;
    }

    private static int saltHash(int hash, int divisor)
    {
      int remainder;
      Math.DivRem(hash, divisor, out remainder);
      return hash + remainder;
    }
  }

And when I asked for a multithreaded version it looked like this:

Threaded HashSet Check
private void RunScript(List<Curve> curves, ref object A)
  {
    A = CurveUtility.RemoveDuplicateCurves(curves);
  }

  // <Custom additional code> 
  public static class CurveUtility
  {
    public static List<Curve> RemoveDuplicateCurves(List<Curve> curves)
    {
      var uniqueCurves = new List<Curve>();
      var hashSet = new HashSet<uint>();
      Parallel.ForEach(curves, curve =>
        {
        // Reverse curve direction where necessary
        if (curve.PointAtStart > curve.PointAtEnd)
          curve.Reverse();

        var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
        lock (hashSet)
        {
          if (!hashSet.Contains(hash))
          {
            hashSet.Add(hash);
            lock (uniqueCurves)
            {
              uniqueCurves.Add(curve);
            }
          }
        }
        });

      return uniqueCurves;
    }

    private static uint CreateCurveHashCodeX3(NurbsCurve crv, int salt)
    {
      int myHash = 0;
      unchecked
      {
        ///Comment or uncomment myHash for varing levels of duplication check
        for (int i = 0; i < crv.Points.Count; i++)
        {
          myHash ^= saltHash(crv.Points[i].Location.GetHashCode(), salt) * (31 * i + 1);
          myHash ^= saltHash(crv.Points[i].Weight.GetHashCode(), salt) * (97 * i + 1);
        }

        for (int i = 0; i < crv.Knots.Count; i++)
        {
          //myHash ^= crv.Knots[i].GetHashCode() * (2 * i + salt);
        }

        myHash ^= crv.Degree * 1559;
        myHash ^= crv.Dimension * 2029;
        //myHash ^= saltHash(crv.Domain.GetHashCode(), salt) * 1699;
        myHash ^= saltHash(crv.IsClosed.GetHashCode(), salt) * 4099;
        myHash ^= saltHash(crv.IsPeriodic.GetHashCode(), salt) * 4447;
        myHash ^= saltHash(crv.IsValid.GetHashCode(), salt) * 5557;
        myHash ^= crv.SpanCount * 9199;
      }
      return (uint) myHash;
    }

    private static int saltHash(int hash, int divisor)
    {
      int remainder;
      Math.DivRem(hash, divisor, out remainder);
      return hash + remainder;
    }
  }

Finally, to get an extra speed bump, you can put the code into a custom component to avoid the C# component casting. This ends up being about 20x faster than the threaded Curve.GeometryEquals() method posted above.

-Brian

1 Like

Amazing! @Brian_Washburn
can you send this*. gh file Or (code) for Custom GH Threaded HashSet Check ?[speed= 81 ms] and does you chek it by NurbsCurves?(not Line)
And can we use similar mechanism this code for Remove dup Brep or other Geometry?

System.Threading.Tasks.Parallel_BW2.gh (234.6 KB)

Yes, this code works with nurbs curves and will actually work slower with lines because it will need to convert the lines to curves. In theory this mechanism would work for other geometry types like Breps by creating hash codes around the different Brep parameters. You could then build a switch case statement for the different geometry types.

This is the code for the custom GH component. Change the namespace, component category and GUID as needed.

RemoveDuplicateCurves
using Grasshopper.Kernel;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace HeronSandbox
{
    public class RemoveDuplicateCurves : GH_Component
    {
        public RemoveDuplicateCurves()
          : base("RemoveDuplicateCurves", "RDC",
              "Description",
              "Heron", "Subcategory")
        {
        }

        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddCurveParameter("Curves", "C", "List of curves from which to remove duplicates.", GH_ParamAccess.list);
        }

        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddCurveParameter("Culled Curves", "CC", "List of curves without duplicates.", GH_ParamAccess.list);
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            List<Curve> curves = new List<Curve>();
            DA.GetDataList(0, curves);
            DA.SetDataList(0, CurveUtility.RemoveDuplicateCurvesParallel(curves));
        }



        public static class CurveUtility
        {
            public static List<Curve> RemoveDuplicateCurvesParallel(List<Curve> curves)
            {
                var uniqueCurves = new List<Curve>();
                var hashSet = new HashSet<uint>();
                Parallel.ForEach(curves, curve =>
                {
                    // Reverse curve direction where necessary
                    if (curve.PointAtStart > curve.PointAtEnd)
                        curve.Reverse();

                    var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
                    lock (hashSet)
                    {
                        if (!hashSet.Contains(hash))
                        {
                            hashSet.Add(hash);
                            lock (uniqueCurves)
                            {
                                uniqueCurves.Add(curve);
                            }
                        }
                    }
                });

                return uniqueCurves;
            }

            public static List<Curve> RemoveDuplicateCurves(List<Curve> curves)
            {
                var uniqueCurves = new List<Curve>();
                var hashSet = new HashSet<uint>();
                foreach (var curve in curves)
                {
                    // Reverse curve direction where necessary
                    if (curve.PointAtStart > curve.PointAtEnd)
                        curve.Reverse();

                    var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
                    if (!hashSet.Contains(hash))
                    {
                        hashSet.Add(hash);
                        uniqueCurves.Add(curve);
                    }
                }

                return uniqueCurves;
            }

            private static uint CreateCurveHashCodeX3(NurbsCurve crv, int salt)
            {
                int myHash = 0;
                unchecked
                {
                    for (int i = 0; i < crv.Points.Count; i++)
                    {
                        myHash ^= saltHash(crv.Points[i].Location.GetHashCode(), salt) * (31 * i + 1);
                        myHash ^= saltHash(crv.Points[i].Weight.GetHashCode(), salt) * (97 * i + 1);
                    }

                    for (int i = 0; i < crv.Knots.Count; i++)
                    {
                        //myHash ^= crv.Knots[i].GetHashCode() * (2 * i + salt);
                    }

                    myHash ^= crv.Degree * 1559;
                    myHash ^= crv.Dimension * 2029;
                    //myHash ^= saltHash(crv.Domain.GetHashCode(), salt) * 1699;
                    myHash ^= saltHash(crv.IsClosed.GetHashCode(), salt) * 4099;
                    myHash ^= saltHash(crv.IsPeriodic.GetHashCode(), salt) * 4447;
                    myHash ^= saltHash(crv.IsValid.GetHashCode(), salt) * 5557;
                    myHash ^= crv.SpanCount * 9199;
                }
                return (uint)myHash;
            }

            private static int saltHash(int hash, int divisor)
            {
                int remainder;
                Math.DivRem(hash, divisor, out remainder);
                return hash + remainder;
            }
        }


        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                return null;
            }
        }

        public override Guid ComponentGuid
        {
            get { return new Guid("144CA80D-B6FC-42F0-8D05-E2FAEA170828"); }
        }
    }
}

Let me know if you want it as a GHA.

-Brian

1 Like

hi @Brian_Washburn thanks
For closeCurvesReverse()RemoveDuplicateCurves doesn’t support but I add some line to the code also still not correct result also by Curve.Orientation ();

    if(curve.IsClosed)
        {
          var c = curve.DuplicateCurve();
          c.Domain =new Interval(0, 1);
          c = c.Trim(new Interval(0, 0.9998));
          if (c.PointAtStart >= c.PointAtEnd)
            curve.Reverse();}
        else{
          // Reverse curve direction where necessary
          if (curve.PointAtStart > curve.PointAtEnd)
            curve.Reverse();}


System.Threading.Tasks.Parallel_BW1.gh (203.5 KB)

Hi @Rh-3d-p ,
It should work with closed curves as it was. Try modifying the CreateCurveHashCodeX3 function:
System.Threading.Tasks.Parallel_BW3.gh (203.7 KB)

    private static uint CreateCurveHashCodeX3(NurbsCurve crv, int salt)
    {
      int myHash = 0;
      unchecked
      {
        for (int i = 0; i < crv.Points.Count; i++)
        {
          myHash ^= saltHash(crv.Points[i].Location.GetHashCode(), salt) * 1039;
          myHash ^= saltHash(crv.Points[i].Weight.GetHashCode(), salt) * 1049;
        }

        for (int i = 0; i < crv.Knots.Count; i++)
        {
          //myHash ^= crv.Knots[i].GetHashCode() * 1051;
        }

        myHash ^= crv.Degree * 1559;
        myHash ^= crv.Dimension * 2029;
        //myHash ^= saltHash(crv.Domain.GetHashCode(), salt) * 1699;
        myHash ^= saltHash(crv.IsClosed.GetHashCode(), salt) * 4099;
        myHash ^= saltHash(crv.IsPeriodic.GetHashCode(), salt) * 4447;
        myHash ^= saltHash(crv.IsValid.GetHashCode(), salt) * 5557;
        myHash ^= crv.SpanCount * 9199;
      }
      return (uint) myHash;
    }

-Brian

1 Like