任务流程在完成所有工作之前结束

时间:2019-01-11 05:05:35

标签: c# .net multithreading task

我在繁重的操作中无法运行多个任务。 似乎任务进程在所有操作完成之前就被杀死了。

这里的代码是我用来复制问题的示例代码。如果添加类似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));
    }
}

预期结果是列表anotherClassListtestlist的大小相同,并且当列表较小或任务操作的复杂性较小时会发生这种情况。但是,当我增加操作量时,anotherClassList缺少一些索引,有时列表中的某些索引是空对象。

示例结果:

enter image description here

为什么会这样,我有Task.WaitAll

2 个答案:

答案 0 :(得分:1)

您的问题是它只是线程不安全的,您不能在多线程环境中添加到列表中并期望它发挥良好的作用。

一种方法是使用lockthread safe collection,但我认为这应该全部重构(我的OCD遍地都是)

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(...);
      }
   }

}

简而言之,我认为您需要更好地考虑方法的线程逻辑,确定您要实现的目标,以及如何从概念上使它成为线程安全的(同时保持性能提升)

答案 1 :(得分:0)

在TheGeneral告诉我List不是线程安全的之后,我将要添加到线程中的List更改为Array类型,并解决了我的问题。