C # перекрестный вызов из обработчика событий - PullRequest
0 голосов
/ 04 января 2012

Я новичок в C # и событиях / делегатах.У меня есть тестовое приложение, которое я создал, которое имитирует то, что я пытаюсь сделать в более крупном проекте.Я продолжаю получать необработанное StackOverflowException при выполнении этого, и я вполне могу понять, почему.Я попытался изменить BeginInvok на Invoke, но, похоже, приложение полностью зависает.Разве я не настроил это правильно?

Вот код формы Windows:

public partial class Form1 : Form
{

    delegate void AddProcessingEventItemCallback(string eventMessage);

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        TestClass t = new TestClass();
        t.OnJobTaskStatusUpdate += new TestClass.JobTaskStatusUpdate(JobTaskStatusUpdateHandler);

        Thread ts = new Thread(new ThreadStart(t.RunTest));
        ts.Start();
        ts.Join();
    }

    private void JobTaskStatusUpdateHandler(object sender, string statusMessage)
    {
        AddProcessingEventItem(statusMessage);
        Application.DoEvents();
    }

    private void AddProcessingEventItem(string eventMessage)
    {
        if (this.listBox1.InvokeRequired)
        {
            AddProcessingEventItemCallback d = new AddProcessingEventItemCallback(AddProcessingEventItem);
            this.BeginInvoke(d, new object[] { eventMessage });
        }
        else
        {
            listBox1.SelectedIndex = listBox1.Items.Add(eventMessage);
            Application.DoEvents();
        }
    }
}

, а вот код в классе:

public class TestClass
{

    public delegate void JobTaskStatusUpdate(object sender, string statusMessage);
    public event JobTaskStatusUpdate OnJobTaskStatusUpdate;

    public void RunTest()
    {

        for (int i = 1; i <= 500; i++)
        {
            UpdateJobTaskStatus(i.ToString(), true);
            //System.Threading.Thread.Sleep(1000);
        }
    }

    internal void UpdateJobTaskStatus(string statusMessage, bool addStatusTime)
    {
        UpdateJobTaskStatus(statusMessage, addStatusTime, false);
    }

    internal void UpdateJobTaskStatus(string statusMessage, bool addStatusTime, bool addLineSpacer)
    {
        OnJobTaskStatusUpdate(this, addStatusTime ? string.Format("{0} :\t{1}", DateTime.Now.ToString(), statusMessage) : string.Format("\t\t\t{0}", statusMessage));
        if (addLineSpacer)
            OnJobTaskStatusUpdate(this, "\t\t\t");
    }
}

Iискал это некоторое время сейчас.Любая помощь будет принята с благодарностью.

Ответы [ 3 ]

3 голосов
/ 04 января 2012

Ваша проблема начинается здесь:

private void button1_Click(object sender, EventArgs e)
{
    ...
    Thread ts = new Thread(new ThreadStart(t.RunTest));
    ts.Start();
    ts.Join();   // blocks the main thread
}

Join() паркует ваш главный поток, и это заставляет вас делать аварийный ремонт с DoEvents() повсюду.Вполне возможно, что StackOverflow происходит из-за слишком большого количества повторных вызовов DoEvents (), я не уверен.

Но, кажется, лучшее решение: использовать BackgroundWorker.

2 голосов
/ 04 января 2012

Ваш код содержит косвенную рекурсию, которую нелегко обнаружить.Происходит что-то вроде этого:

Когда вы нажимаете кнопку, ваш основной поток запускает новый поток и ожидает его завершения.Рабочий поток будет выполняться и выполнять цикл в TestClass n раз.В каждой итерации он ставит новый AddProcessingEventItemCallback в очередь через Control.BeginInvoke.

Поскольку основной поток заблокирован в ts.Join, однако он не может вызвать делегат.(Именно поэтому ваша программа будет блокироваться на неопределенный срок, когда вы выполняете Invoke вместо BeginInvoke. Рабочий поток ожидает, пока основной поток завершит выполнение обратного вызова, в то время как основной поток ожидает, пока рабочий поток завершит выполнение.)

В конце концов рабочий поток завершит свою работу, и основной поток вернется из вызова ts.Join.Теперь он начинает выполнять первый из обратных вызовов в очереди.Хотя он находится внутри метода AddProcessingEventItem, основной поток вызывает Application.DoEvents() (вторая строка в предложении «else»), что заставляет его обрабатывать следующий делегат AddProcessingEventItemCallback.Таким образом, основной поток рекурсивно (и косвенно) вызывает AddProcessingEventItem n раз (n - это число итераций цикла в вашем TestClass), этого достаточно, чтобы уничтожить ваш стек.

Если вы просто удалите вызов Application.DoEvents вelse-пункт ваша программа будет работать без перегрузки стека.Однако, как указал Хенк, лучшим решением будет вовсе не блокировать ваш основной поток и полностью избавиться от вызовов Application.DoEvents.(Также см. Ответ Джона здесь . Особенно обратите внимание на последнее предложение в его ответе.)

0 голосов
/ 04 января 2012

Ваша проблема заключается в ts.Join (), который блокирует ваш поток GUI. BackgroundWorker не является необходимым, но вам следует избегать Application.DoEvents () и реализовывать свою логику в отдельном потоке. Если вам нужна какая-либо синхронизация / ожидание, используйте мьютекс / семафор, ManualResetEvent и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...