C # Передача массива строкового элемента в Task.Run - PullRequest
0 голосов
/ 30 сентября 2018

Попытка передать элемент массива строк в функцию, которая вызывается в Task.Run.Кто-нибудь знает, в чем здесь ошибка?

Код здесь не работает, он ведет себя так, как будто ProcessElem никогда не вызывается.

string[] arr = message.Split(new string[] {"\n"}, StringSplitOptions.None);

for (int i = 0; i < arr.Length; i++) {
    if(arr[i] != "") {
       var t = Task.Run(() => this.ProcessElem(arr[i]));
    }
 }

Однако приведенный ниже код работает

string[] arr = message.Split(new string[] {"\n"}, StringSplitOptions.None);

for (int i = 0; i < arr.Length; i++) {
    if(arr[i] != "") {
       var tmp = arr[i];
       var t = Task.Run(() => this.ProcessElem(tmp));
    }
 }

Я очень новичок в том, как C # делает вещи, но кажется, что оба шаблона небезопасны, потому что функция, которая вызывает Task.Run() может вернуться до выполнения функции ProcessElem, и если строки передаются по ссылке, они будут уничтожены до вызова ProcessElem.

Если это так, как лучше всего передать строку в ProcessElem?

Кроме того, почему первая версия на самом деле не "вызывает" ProcessElem?У меня есть оператор печати в верхней части ProcessElem, и он печатается только во второй версии.

Ответы [ 3 ]

0 голосов
/ 30 сентября 2018

Ваша проблема - давняя проблема, ее работа с lamdas, и она очень хорошо документирована.

Однако, если вы просто создаете и ожидаете кучу задач, то сохраните свой собственный код, хлопоты,и создание задачи и просто используйте TPL Parallel.For или AsParallel

Parallel.For(0, arr.Length, (i) => ProcessElem(arr[i]));

Или

arr.AsParallel().ForAll(ProcessElem);

Или, если вы действительно не хотите пустых строк

arr.Where(x => !string.IsNullOrEmpty(x))
   .AsParallel()
   .ForAll(ProcessElem);
0 голосов
/ 30 сентября 2018

Добро пожаловать в захваченные переменные .

Task.Run(() => this.ProcessElem(arr[i]))

По сути это означает:

  1. Выполните мое лямбда-действие: () => this.ProcessElem(arr[i])

  2. Запустите его после того, как вы нашли / создали поток для этого. то есть через некоторое время .

Однако задействована только одна переменная i, и она определена вне области действия лямбда-действия,она не копируется, та же самая переменная просто захватывается и ссылается на нее.

К тому моменту, когда поток приступает к выполнению, значение i имеетскорее всего изменилось.Обычно цикл завершается за до того, как выполнят свою работу.

Это означает, что к этому времени i равняется arr.Length, и все потоки пытаются получить доступ к arr[arr.length], что, очевидно, приводит кв IndexOutOfRangeException.

Когда вы делаете var tmp = arr[i];, вы создаете свежую переменную для каждой итерации цикла, копируете переменную цикла и захватываете эту копию в своей лямбде, именно поэтому она работает.

0 голосов
/ 30 сентября 2018

Источником вашей проблемы является то, как фактические «сопрограммы» работают в C #

i не передается как текущее значение, а как ref i, что означает, что ваш Action всегда будет получатьтекущее значение i при его выполнении.

Скорее всего, вы выполняете этот код, а задачи не выполняются параллельно.Это означает, что конкретная выполненная задача получает текущее значение i, которое в большинстве простых случаев будет соответствовать условию выхода: arr.Length + 1

для доказательства:

for (int i = 0; i < arr.Length; i++)
{
    if (arr[i] != "")
    {
        var j = i;
        var t = Task.Run(() => ProcessElem(arr[j]));
        tasklist.Add(t);
    }
}

будет отлично работать (если у вас нет проблем в вашем ProcessElem методе: P)

в отношении уничтожения строк, если вы не получили какой-либо объект, который реализует IDisposable, вам будет хорошо с передачейэто в какую-то лямбду.Он будет существовать до тех пор, пока не будет удалена настоящая лямбда (поскольку он сохранит некоторую ссылку на объект, например, в этом случае arr)

...