繁体   English   中英

来自事件处理程序的C#跨线程调用

[英]C# cross thread call from an event handler

我是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");
    }
}

我已经搜索了一段时间了。 任何帮助将不胜感激。

您的问题从这里开始:

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。

您的代码包含一个不容易发现的间接递归。 发生的事情是这样的:

单击该按钮时,主线程将启动一个新线程,并等待其完成。 工作线程将沿着TestClass运行并执行n次循环。 在每次迭代中,它将通过Control.BeginInvoke排队新的AddProcessingEventItemCallback。

由于主线程在ts.Join中被阻止,但是它无法调用委托。 (这也是为什么当您执行Invoke而不是BeginInvoke时,程序将无限期阻塞的原因。工作线程正在等待主线程完成回调的执行,而主线程正在等待工作线程完成执行。)

最终,工作线程将完成其工作,并且主线程从调用返回到ts.Join。 现在,它开始执行排队的第一个回调。 当它在AddProcessingEventItem方法内部时,主线程调用Application.DoEvents() (“ else”子句中的第二行),这使其处理下一个AddProcessingEventItemCallback委托。 因此,主线程递归(间接)调用n次AddProcessingEventItem(n是TestClass中循环迭代的次数),足以破坏堆栈。

如果仅删除else子句中对Application.DoEvents的调用,则程序将在不破坏堆栈的情况下运行。 但是,正如Henk指出的那样,更好的解决方案是根本不阻塞主线程并完全摆脱Application.DoEvents调用。 (另见乔恩的答案在这里 ,特别要注意在他的回答中最后一句。)

您的问题出在ts.Join(),它阻塞了您的GUI线程。 BackgroundWorker不是必需的,但是您应该避免Application.DoEvents()并在单独的线程中实现逻辑。 如果需要任何同步/等待,请使用互斥/信号量,ManualResetEvent等。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM