Почему BackgroundWorker выдает исключение, а мое простое потоковое решение — нет?

#c# #.net #multithreading

#c# #.net #многопоточность

Вопрос:

Мы стремимся избежать невосприимчивости нашего пользовательского интерфейса к потенциально интенсивному вызову базы данных. Я экспериментировал с различными способами достижения следующего сценария.

  1. Пользователь вызывает функциональность.
  2. Разветвляется поток, который выполняет функциональность.
  3. У пользователя есть возможность отменить функциональность.
  4. a. Если пользователь не отменяет, поток работает с независимыми данными и порождает какой-то другой, не связанный пользовательский интерфейс.
  5. b. Если пользователь отменяет, отмените обработку потока и отмените любое связанное действие (например, хранимую процедуру SQL, которую мы будем вызывать — не имеет значения для целей этого вопроса).

Будучи парнем низкого уровня, вроде C , я сначала попробовал решение с прямым потоком. Затем я попытался изучить .Решение NET для BackgroundWorker (C #).

Вот код, который я использую для имитации этой потенциально трудоемкой операции, которую должен обрабатывать поток.

     /// <summary>
    /// Simulates the processing for a query that is pretty much
    /// unresponsive after it's been invoked.
    /// </summary>
    public void SimulateSuperLongQuery ()
    {
        // Pass the time, for a very long time...
        for (int i=0; i < int.MaxValue; i  )
            SimulateSuperLongQuery();

        _completed = true;
    }
  

Особенно…

  • Мое простое потоковое решение отлично работает при вызове этого метода.
  • Решение BackgroundWorker выдает исключение StackOverflowException при вызове этого метода!

У кого-нибудь есть идеи, почему BackgroundWorker вызывает исключение?


Код, на случай, если это поможет. Вот немного кода BackgroundWorker. Это прямо с веб-сайта MSDN.

         BackgroundWorker bw = new BackgroundWorker();
        bw.WorkerSupportsCancellation = true;
        bw.DoWork  = new DoWorkEventHandler(bw_DoWork);

        if (bw.IsBusy != true)
        {
            bw.RunWorkerAsync();
        }

        // ...

    private void bw_DoWork (object sender, DoWorkEventArgs e)
    {
        QuerySimulator query = new QuerySimulator();
        query.SimulateSuperLongQuery();
    }
  

Вот немного моего простого ванильного кода.

         // Fire up a thread to run a query
        QuerySimulator query = new QuerySimulator();
        Thread queryThread = new Thread(query.SimulateSuperLongQuery);
  

Комментарии:

1. Ваш код правильный — у вас есть SimulateSuperLongQuery вызов самого себя — Я удивлен, что простая Thread версия не переполняет стек.

2. Я предполагаю, что вы имеете в виду «нет», поскольку мое простое потоковое решение не переполняет стек. Да! У меня было такое же подозрение; работает нормально. Исключение выдается только для решения BackgroundWorker. Предназначен самозваный вызов.

3. отличное место и в 5-минутном окне редактирования!

4. Нет причин, по которым этот метод должен работать независимо от того, на чем он запущен. Он никогда не завершится и никогда не перестанет вызывать себя. Не только это, но цикл даже никогда не достигнет значения i = 1, потому что каждый вызов просто запускает новый цикл и немедленно вызывает сам себя в бесконечном рекурсивном цикле, который всегда будет приводить к переполнению стека.

5. Какое конечное условие должно остановить рекурсию?

Ответ №1:

Как люди уже заметили, ясно, что вы столкнетесь с Stackoverflow, потоковым или нет — просто в однопоточном случае я подозреваю, что среда CLR смогла выполнить оптимизацию конечных вызовов.

Если вы хотите имитировать блокирующий поток, сделайте именно это: заблокируйте его.

 Thread.Sleep(TimeSpan.FromSeconds(5));
  

Комментарии:

1. Метод sleep вернул бы управление любому потоку, который я разветвил, нет? Если это точно, то на самом деле это не будет эмулировать среду, для которой я ищу лучшее решение.

2. Нет, thread.sleep блокирует поток, в котором он вызывается. Я уверен, что это хорошо имитирует то, что вы хотите сделать.

3. Я думаю, мне нужно было бы знать, куда вы хотите его поместить, прежде чем быть уверенным в этом, или попросить вас определить «имитировать блокирующий поток». Я хочу, чтобы мой поток пользовательского интерфейса продолжался, никогда не переходя в режим ожидания, поэтому ему там не место. Я хочу, чтобы мой разветвленный поток потенциально выполнял бесконечные вычисления / исполнения, поэтому его размещение там вернуло бы управление обратно потоку пользовательского интерфейса. Это дало бы мне удобный сценарий, в котором пользовательский интерфейс является отзывчивым, нет?

4. Конечно, если вы переводите поток пользовательского интерфейса в режим ожидания, он блокируется. Но вы хотите, чтобы какой-то поток некоторое время не возвращался, верно? Нитки. Sleep сделает именно это…

5. Итак, поток. Режим ожидания эквивалентен потоку, выполняющему работу? Я хочу имитировать разветвленный поток, выполняющий работу. Если они на самом деле одно и то же, тогда я буду использовать код. В конце концов, это меньше, чем то, что у меня есть в настоящее время.

Ответ №2:

Не видя всего кода, сложно сказать наверняка, но я почти уверен, что ваш код SimulateSuperLongQuery вызовет исключение StackOverflowException, несмотря ни на что. Он вызывает себя рекурсивно вечно (или пытается, во всяком случае).

Сколько времени требуется для запуска вашего простого потокового решения? Если вы не видите исключения в этой версии, возможно, оно просто где-то проглатывается.

Комментарии:

1. Тьфу, я пропустил запрос. Метод Start(). Всем спасибо за информацию. Нужно подождать 2 минуты, прежде чем я смогу принять этот ответ.