Потому что по факту ты не передаёшь значение счётчика как аргумент (метод же тут же не выполняется), ты "захватываешь" переменную, которая там где-то потом сработает. Так вот к моменту когда дело до этого доходит, счётчик уже равен 10, его ты и видишь.
Как вариант ты можешь например добавить промежуточную переменную j, которую ты приравниваешь к i и отдавать её. В общем тебе надо создать отдельную структуру куда ты сложишь параметры для таска и отдашь его уже туда.
П.С.
Если вот глянуть что на самом деле выходит после компиляции
private sealed class <>c__DisplayClass0_0
{
public int i;
internal string <Main>b__0()
{
return GetData(i);
}
}
public static void Main()
{
int num = 5;
Task<string>[] array = new Task<string>[num];
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.i = 0;
while (<>c__DisplayClass0_.i < num)
{
array[<>c__DisplayClass0_.i] = Task.Run(new Func<string>(<>c__DisplayClass0_.<Main>b__0));
<>c__DisplayClass0_.i++;
}
}
то видно, что счётчик цикла это поле скрытого класса, у которого всего 1 экземпляр, который создаётся до цикла. И наш делегат для такс читает значение этого поля, когда доходит его очередь выполняться. К тому времени счётчик достигает конца.
Если же мы создаём промежуточную переменную, то в коде уже для каждого такса создаётся свой индивидуальный экземпляр скрытого класса, поэтому и значение будет то, которое было.
int num2 = 0;
while (num2 < num)
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.j = num2;
array[num2] = Task.Run(new Func<string>(<>c__DisplayClass0_.<Main>b__0));
num2++;
}