不同的任务方法以不同的方式工作

Different Task approaches work differently

我想问一下为什么这两种方法不同并且return有两组不同的值:

第一个在我看来是正确的 return 值从 0 到 8 并且适用于不同的线程(LINQPad 代码):

void Main()
{
    var newTasks = Enumerable.Range(0, 9).Select(x => Task.Run(() => DoSomething(x)));

    Task.WhenAll(newTasks);
}    

public int DoSomething(int value)
{
    return value;
}

其次,在我看来这是不正确的 returns 随机值特别是 9 但也适用于不同的线程。

void Main()
{
    var tasks = new List<Task<int>>();
    
    for (var index = 0; index < 9; index++)
    {
        var task = Task.Run(() => DoSomething(index));
        
        tasks.Add(task);
    }
    
    Task.WaitAll(tasks.ToArray());
}    

public int DoSomething(int value)
{
    return value;
}

是否可以修改第二个并得到与第一个示例类似的结果?

感谢您的回答。

Is it possible to modify the second one and get the result similar to first example?

当您将变量或对象传递给 Task.Run(()=> DoSomething(index)) 时,您 捕获 一个变量。

从某种意义上说,您不只是将该变量作为值或对象发送,而是让该线程直接访问原始变量。 (这是高度概括的并且有很多细微差别)。

您最有可能遇到的问题是 CLR 正在尝试捕获 index,这是一个 迭代器,其范围不能离开它的边界,除非有任何尝试这样做会保留在堆栈上(基本上是同一个线程,再次泛化)。

当 CLR 'captures' index 时,它只会捕获 index 的随机值(由于竞争条件) - 或者 lastindex 的值,这是 double 不直观,因为你明确告诉它 index 不应该在这一行达到 9这里 for (var index = 0; index < 9; index++).

这是一个棘手的事情,当 CLR 捕获 index 时,它会捕获 index 的最后一个值。但是在循环结束后 index 又增加了一次导致 9 成为 index.

的最后一个值

这可能会导致一大堆问题,尤其是当您使用数组时,我们来了 IndexOutOfBoundsException

如何修复
修复实际上非常简单!只需在循环中创建一个临时变量,然后直接传递它而不是 index

这是一个例子:

void Main()
{
    var tasks = new List<Task<int>>();
    
    for (var index = 0; index < 9; index++)
    {
        int tmpIndex = index;

        var task = Task.Run(() => DoSomething(tmpIndex ));
        
        tasks.Add(task);
    }
    
    Task.WaitAll(tasks.ToArray());
}

Is it possible to modify the second one and get the result similar to first example?

旁注
永远不要依赖任务中的数据是有序的,除非您明确实施了项目按顺序返回的保证TaskScheduler通常在FIFO中选择Tasks到运行(先进先出),但是,如果TaskScheduler优化,优先,或转移任务(经常这样做)你的任务将按顺序完成,随后你得到的结果将不按顺序。