Многопоточность цикла for проходит верхнюю границу

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

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

При выводе счетчика на индикатор выполнения и обновлении индикатора выполнения я получил это в своем окне вывода.

при обновлении индикатора выполнения

Если я прокомментировал обновление на индикаторе выполнения (pbLoad.Value = i;), я получил это в своем окне вывода.

без обновления индикатора выполнения

Я попытался изменить цикл на i<101, а также попытался переместиться туда, где был i++, но это не имело никакого значения.

РЕДАКТИРОВАТЬ: Это исходит из файла BeginInvoke. Когда я переключил его на Invoke, это сработало, но затем я попаду в тупик при попытке использовать кнопку отмены.

Вот код:

public partial class Form1 : Form
    {
        Thread backgroundThread;
        bool stopExecution = false;

        public Form1()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            stopExecution = false;
            btnStart.Enabled = false;
            backgroundThread = new Thread(DoDomethingThatTakesAWhile);
            backgroundThread.Start();
        }

        private void DoDomethingThatTakesAWhile()
        {
            for (int i = 0; i <= 100; i++)
            {
                if (!stopExecution)
                {
                    Thread.Sleep(100);

                    if (pbLoad.InvokeRequired)
                    {
                        MethodInvoker myMethod
                            = new MethodInvoker(
                                delegate
                                {
                                    if (!stopExecution)
                                    {
                                        pbLoad.Value = i;
                                        Debug.WriteLine(i); //i to output window                                        
                                    }
                                });
                        pbLoad.BeginInvoke(myMethod);
                    }
                    else
                    {
                        pbLoad.Value = i;
                    }
                }
                else
                {
                    break;
                }
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            //backgroundThread.Abort();
            stopExecution = true;
            backgroundThread.Join();

            pbLoad.Value = 0;
            btnStart.Enabled = true;
        }
    }

person Jacob Goulden    schedule 05.02.2014    source источник
comment
звучит как состояние гонки, поток увеличивает значение до того, как цикл сможет полностью завершить обработку.   -  person Pseudonym    schedule 05.02.2014
comment
Однако значение изменяется и доступно только в этом одном потоке.   -  person Jacob Goulden    schedule 05.02.2014


Ответы (2)


Когда вы вызываете MethodInvoke, это происходит не в этот момент, а через некоторое время.

В вашем сценарии у вас есть шанс, что произойдет следующее:

  • вызванный код наконец выполняется;
  • цикл уже завершен (и i становится 101)
  • вы обращаетесь к i напрямую и читаете 101.

И чтобы исправить это, вы можете сделать копию i (передав его в качестве параметра вызываемому методу):

pbLoad.BeginInvoke(new Action<int>(a =>
{
    if (!stopExecution)
    {
        pbLoad.Value = a;
        Debug.WriteLine(a); //a to output window                                        
    }
}), new object[] { i });

P.S: вам не нужно проверять InvokeRequired, если только вы не планируете напрямую вызывать метод DoDomethingThatTakesAWhile, что, я полагаю, не так.

person Sinatr    schedule 05.02.2014

Вы используете BeginInvoke, что явно открывает возможность для гонок. Я рекомендую синхронный вызов.

Кроме того, вы фиксируете i, а не его значение. Это колоритно и работает только случайно, потому что вы спите.

Любое из изменений решит проблему. Делайте оба.

Если можете, отмените это низкоуровневое использование синхронизации и используйте async/await.

person usr    schedule 05.02.2014
comment
Я совершенно уверен, что Task - это то, что нужно сегодня (не async/await). Но мне грустно, что все говорят wpf в winforms темах и taks в thread темах. Я плачу тогда. - person Sinatr; 05.02.2014
comment
@Sinatr await — это механизм, используемый для возобновления работы после завершения задачи. Эти две техники дополняют друг друга. Вы по-прежнему можете использовать задачи с поддержкой потоков, которые по-прежнему полезны. - person usr; 05.02.2014