Почему помещение DoEvents в цикл вызывает исключение StackOverflow?

StackOverflow https://stackoverflow.com//questions/20003969

Вопрос

Я получал странную ошибку в устаревшем приложении (написанном не мной), где я получал исключение StackOverflow при изменении даты в календаре.

Упрощенная версия приведена ниже.Это исходный код формы Windows, содержащей два элемента управления, метку под названием метка2 и календарь под названием MonthCalendar называется Месяцкалендарь1.

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

private void monthCalendar1_DateChanged(object sender, DateRangeEventArgs e)
{
    const string sTextDisplay = "Press Generate button to build *** Reports ... ";

    for (var i = 0; i < 45; i++)
    {
        label2.Text = Mid(sTextDisplay, 1, i);
        System.Threading.Thread.Sleep(50);

        //Error on this line
        //An unhandled exception of type 'System.StackOverflowException' occurred in System.Windows.Forms.dll
        Application.DoEvents();
    }
}

public static string Mid(string s, int a, int b)
{
    var temp = s.Substring(a - 1, b);
    return temp;
}

Я не вижу трассировки стека, все, что я вижу, это:

{Невозможно вычислить выражение, поскольку текущий поток находится в состоянии переполнения стека.}

Кроме того, меня интересуют комментарии, спрашивающие, почему я не проверил трассировку стека моего исключения StackOverflow, как это выглядит это невозможно по крайней мере, без сторонних инструментов.

Что может быть причиной этого?Спасибо

Это было полезно?

Решение

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

Важно помнить, что программный стек достаточно велик, но конечный.Вы можете поместить в стек только столько вызовов функций, сколько потребуется, прежде чем в нем закончится место.Это то, что происходит, когда мы говорим, что стек переполняется.

DoEvents() это просто еще один вызов функции.Вы могли бы включить это в длительную задачу, чтобы позволить вашей программе обрабатывать сообщения от операционной системы об активности пользователя:такие вещи, как щелчки, нажатия клавиш и т.д.Это также позволяет вашей программе обрабатывать сообщения из операционной системы, например, если программе необходимо заново отрисовать свою Windows.

Обычно будет только одно или два (или даже ноль) сообщения, ожидающих DoEvents() позвони.Ваша программа обрабатывает их, DoEvents() вызов извлекается из стека, и исходный код продолжается.Иногда может быть много ожидающих сообщений.Если Любой одно из этих сообщений также приводит к запуску кода, который снова вызывает DoEvents(), Таким образом, теперь мы находимся на другом глубоком уровне в стеке вызовов.И если этот код, в свою очередь, обнаружит ожидающее сообщение, которое вызывает DoEvents() чтобы убежать, мы поднимемся еще на один уровень глубже.Может быть, ты сможешь понять, к чему это ведет.

DoEvents(), используемый в сочетании с MouseMove событие, является распространенным источником подобных проблем. MouseMove события могут накапливаться на вас очень быстро.Это также может произойти с KeyPress события, когда у вас есть клавиша, которая удерживается нажатой.

Обычно я бы не ожидал наличия Календаря DateChanged событие, чтобы иметь такого рода проблемы, но если у вас есть DoEvents() где-нибудь в другом месте или организуйте другое событие (возможно, на вашем лейбле), которое, в свою очередь, обновит ваш календарь, вы можете легко создать цикл, который заставит вашу программу перейти по спирали в ситуацию переполнения стека.

Вместо этого вы хотите исследовать Фоновый кодер компонент или более новый Task и async Шаблоны.

Возможно, вы также захотите прочитать мою статью о DoEvents() по этому вопросу:

Как использовать DoEvents (), не будучи "злым"?

Другие советы

Обычно у вас есть перекачка сообщений довольно близко к вершине стека.Добавление большого количества сообщений никогда не приводит к созданию "глубокого" стека, поскольку все они обрабатываются насосом верхнего уровня.Используя DoEvents создает новую перекачку сообщений в более глубокой точке стека.Если одно из сообщений, которые вы перекачиваете, также вызывает DoEvents, теперь у вас есть перекачка сообщений даже еще глубже в стопке.Если у этого сообщения pump есть другое сообщение, которое вызывает DoEvents ...и вы уловили идею.

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

Проблема здесь в том, что ваш код не упрощает задачу.Это зовет DoEvents a лот в цикле, поэтому ему нужно некоторое время находиться в очереди ожидания, чтобы действительно выйти из этого цикла.Кроме того, если у вас есть "активное" приложение, которое отправляет много сообщений в очередь сообщений, возможно, много monthCalendar1_DateChanged события или даже другие события, использующие DoEvents в цикле или просто в других событиях, чтобы очередь не была пустой, не особенно сложно поверить, что ваш стек станет достаточно глубоким, чтобы привести к SOE.

Идеальным решением, конечно, является не использовать DoEvents.Вместо этого напишите асинхронный код, чтобы глубина вашего стека никогда не превышала постоянного значения.

Doevents не следует использовать в любом случае, и вам не требует подстроки, чтобы архивировать эффект машиностроения

Вот лучший способ, которым я знаю на данный момент:

    using System.Threading;



    private string text = "this is my test string";
    private void button1_Click(object sender, EventArgs e)
    {
        new Thread(loop).Start();

    }

    private void loop()
    {
        for (int i = 0; i < text.Length; i++)
        {
            AddChar(text[i]);
            Thread.Sleep(50);
        }
    }

    private void AddChar(char c)
    {
        if (label1.InvokeRequired)
            Invoke((MethodInvoker)delegate { AddChar(c); });
        else
            label1.Text += c;
    }
.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top