Завершение процесса до завершения всей работы - PullRequest
0 голосов
/ 11 января 2019

У меня были проблемы с выполнением нескольких задач с тяжелыми операциями. Похоже, что процессы задачи убиты до того, как все операции завершены.

Код здесь является примером кода, который я использовал для репликации проблемы. Если я добавлю что-то вроде Debug.Write (), добавленное ожидание записи устранит проблему. Проблема исчезнет, ​​если я проведу тестирование на меньшем размере выборки. Причина, по которой в приведенном ниже примере есть класс, заключается в создании сложности для теста. Реальный случай, когда я впервые столкнулся с проблемой, слишком сложен, чтобы объяснить это для поста здесь.

public static class StaticRandom
{
    static int seed = Environment.TickCount;

    static readonly ThreadLocal<Random> random =
        new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref seed)));

    public static int Next()
    {
        return random.Value.Next();
    }

    public static int Next(int maxValue)
    {
        return random.Value.Next(maxValue);
    }

    public static double NextDouble()
    {
        return random.Value.NextDouble();
    }
}

// this is the test function I run to recreate the problem:
static void tasktest()
{
    var testlist = new List<ExampleClass>();
    for (var index = 0; index < 10000; ++index)
    {
        var newClass = new ExampleClass();
        newClass.Populate(Enumerable.Range(0, 1000).ToList());
        testlist.Add(newClass);
    }
    var anotherClassList = new List<ExampleClass>();
    var threadNumber = 5;

    if (threadNumber > testlist.Count)
    {
        threadNumber = testlist.Count;
    }

    var taskList = new List<Task>();
    var tokenSource = new CancellationTokenSource();
    CancellationToken cancellationToken = tokenSource.Token;

    int stuffPerThread = testlist.Count / threadNumber;
    var stuffCounter = 0;
    for (var count = 1; count <= threadNumber; ++count)
    {
        var toSkip = stuffCounter;
        var threadWorkLoad = stuffPerThread;
        var currentIndex = count;

        // these ifs make sure all the indexes are covered
        if (stuffCounter + threadWorkLoad > testlist.Count)
        {
            threadWorkLoad = testlist.Count - stuffCounter;
        }
        else if (count == threadNumber && stuffCounter + threadWorkLoad < testlist.Count)
        {
            threadWorkLoad = testlist.Count - stuffCounter;
        }

        taskList.Add(Task.Factory.StartNew(() => taskfunc(testlist, anotherClassList, toSkip, threadWorkLoad),
            cancellationToken, TaskCreationOptions.None, TaskScheduler.Default));
        stuffCounter += stuffPerThread;
    }

    Task.WaitAll(taskList.ToArray());
}

public class ExampleClass
{
    public ExampleClassInner[] Inners { get; set; }

    public ExampleClass()
    {
        Inners = new ExampleClassInner[5];
        for (var index = 0; index < Inners.Length; ++index)
        {
            Inners[index] = new ExampleClassInner();
        }
    }

    public void Populate(List<int> intlist) {/*adds random ints to the inner class*/}


    public ExampleClass(ExampleClass copyFrom)
    {
        Inners = new ExampleClassInner[5];
        for (var index = 0; index < Inners.Length; ++index)
        {
            Inners[index] = new ExampleClassInner(copyFrom.Inners[index]);
        }
    }

    public class ExampleClassInner
    {
        public bool SomeBool { get; set; } = false;
        public int SomeInt { get; set; } = -1;

        public ExampleClassInner()
        {
        }

        public ExampleClassInner(ExampleClassInner copyFrom)
        {
            SomeBool = copyFrom.SomeBool;
            SomeInt = copyFrom.SomeInt;
        }
    }
}

static int expensivefunc(int theint)
{ 
/*a lot of pointless arithmetic and loops done only on primitives and with primitives, 
just to increase the complexity*/
    theint *= theint + 1;
    var anotherlist = Enumerable.Range(0, 10000).ToList();
    for (var index = 0; index < anotherlist.Count; ++index)
    {
        theint += index;
        if (theint % 5 == 0)
        {
            theint *= index / 2;
        }
    }
    var yetanotherlist = Enumerable.Range(0, 50000).ToList();
    for (var index = 0; index < yetanotherlist.Count; ++index)
    {
        theint += index;
        if (theint % 7 == 0)
        {
            theint -= index / 3;
        }
    }
    while (theint > 8)
    {
        theint /= 2;
    }

    return theint;
}

// this function is intentionally creating a lot of objects, to simulate complexity
static void taskfunc(List<ExampleClass> intlist, List<ExampleClass> anotherClassList, int skip, int take)
{
    if (take == 0)
    {
        take = intlist.Count;
    }
    var partial = intlist.Skip(skip).Take(take).ToList();
    for (var index = 0; index < partial.Count; ++index)
    {
        var testint = expensivefunc(index);
        var newClass = new ExampleClass(partial[index]);
        newDna.Inners[StaticRandom.Next(5)].SomeInt = testint;
        anotherClassList.Add(new ExampleClass(newClass));
    }
}

Ожидаемый результат состоит в том, что список anotherClassList будет иметь тот же размер, что и testlist, и это происходит, когда списки меньше или сложность операций задачи меньше. Однако когда я увеличиваю объем операций, в anotherClassList пропадает несколько индексов, и иногда некоторые индексы в списке являются нулевыми объектами.

Пример результата:

enter image description here

Почему это происходит, у меня Task.WaitAll?

Ответы [ 2 ]

0 голосов
/ 11 января 2019

После того, как TheGeneral проинформировал меня, что списки не являются потокобезопасными, я изменил список, в который я добавлял поток, на тип массива, и это устранило мою проблему.

0 голосов
/ 11 января 2019

Ваша проблема в том, что он не защищен от потоков, вы просто не можете добавить его в список в многопоточной среде и ожидать, что он будет играть хорошо.

Один из способов - использовать lock или потокобезопасную коллекцию , но я чувствую, что все это должно быть подвергнуто рефакторингу (мое ОКР отключается повсюду)

private static object _sync = new object();

...

private static void TaskFunc(List<ExampleClass> intlist, List<ExampleClass> anotherClassList, int skip, int take)
{

   ...

   var partial = intlist.Skip(skip).Take(take).ToList();

   ...

   // note that locking here will likely drastically decrease any performance threading gain
   lock (_sync)
   {
      for (var index = 0; index < partial.Count; ++index)
      {
         // this is your problem, you are adding to a list from multiple threads
         anotherClassList.Add(...);
      }
   }

}

Короче говоря, я думаю, вам нужно лучше подумать о логике многопоточности вашего метода, определить, чего вы пытаетесь достичь, и как сделать его концептуально безопасным для многопоточности (при этом сохраняя прирост производительности)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...