单声道挂起和MS.Net没有的线程问题

时间:2011-04-27 12:26:51

标签: multithreading mono invoke begininvoke

我正在使用单声道测试我的应用程序预装Linux端口,我有一个线程问题。我最初考虑在这里粘贴3000个代码行,但最后我设计了一个小的最小例子;)

你有一个带有按钮的表单(诗意地命名为Button1和一个标签(毫无意外地,名称为Label1))。整个地段都在一个名为Form1的形式上过着幸福的生活。点击Button1会启动一个无限循环,增加本地计数器并更新Label1(使用Invoke)以反映其值。

现在在Mono中,如果您调整表单大小,标签将停止更新,永远不会重新启动。 MS实现不会发生这种情况。 BeginInvoke效果不佳;更糟糕的是,它使UI在两种情况下都挂起。

你知道这种差异来自哪里吗?你会如何解决它?最后,为什么BeginInvoke不在这里工作?我一定是犯了一个大错...但是哪个?

<小时/> 的修改: 到目前为止取得了一些进展:

  • 调用BeginInvoke确实有效;只是,UI只是没有足够快地刷新,所以它似乎停止了。
  • 在单声道上,当您在UI队列中插入消息时(例如通过调整表单大小),整个线程会挂起。实际上,同步Invoke调用永远不会返回。我想了解原因。
  • 有趣的是:即使使用BeginInvoke,在调整大小操作结束之前也不会执行异步调用。在MS.Net上,它们在调整大小时继续运行。

代码看起来像这样(C#版本更低):

Public Class Form1
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim T As New Threading.Thread(AddressOf Increment)
        T.Start()
    End Sub

    Sub UpdateLabel(ByVal Text As String)
        Label1.Text = Text
    End Sub

    Delegate Sub UpdateLabelHandler(ByVal Text As String)
    Sub Increment()
        Dim i As Long = 0
        Dim UpdateLabelDelegate As New UpdateLabelHandler(AddressOf UpdateLabel)
        Try
            While True
                i = (i + 1) Mod (Long.MaxValue - 1)
                Me.Invoke(UpdateLabelDelegate, New Object() {i.ToString})
            End While
        Catch Ex As ObjectDisposedException
        End Try
    End Sub
End Class

或者,在C#中,

public class Form1
{
    private void Button1_Click(System.Object sender, System.EventArgs e)
    {
        System.Threading.Thread T = new System.Threading.Thread(Increment);
        T.Start();
    }

    public void UpdateLabel(string Text)
    {
        Label1.Text = Text;
    }

    public delegate void UpdateLabelHandler(string Text);
    public void Increment()
    {
        long i = 0;
        UpdateLabelHandler UpdateLabelDelegate = new UpdateLabelHandler(UpdateLabel);
        try {
            while (true) {
                i = (i + 1) % (long.MaxValue - 1);
                this.Invoke(UpdateLabelDelegate, new object[] { i.ToString() });
            }
        } catch (ObjectDisposedException Ex) {
        }
    }
}

3 个答案:

答案 0 :(得分:5)

这是单声道运行时的错误,至少我认为是这样。代码可能不是一个好的做法(我不是一个线程专家),但暗示一个错误的事实是Windows和Linux上的行为不同。

在Linux上,mono与MS.Net在Windows上的行为完全相同。即使在调整大小时也不会挂起,不断更新。

在Windows上,mono显示上述所有问题。我在https://bugzilla.novell.com/show_bug.cgi?id=690400发布了一个错误报告。

答案 1 :(得分:1)

  

你知道这种差异在哪里吗?   来自?你会如何解决它?

我不确定。我没有在代码中看到任何明显会导致Mono和.NET之间差异的内容。如果我不得不做出一个疯狂的猜测,我会说你有可能偶然发现Mono中一个不起眼的错误。虽然,我认为Mono可能使用一种完全不同的机制来处理导致表单刷新的WM_PAINT消息。从重复调用Invoke开始持续冲击UI线程可能会破坏Mono刷新表单的能力。

  

最后,为什么不是BeginInvoke   在这里工作?

在紧密循环中调用Invoke已经够糟糕了,但BeginInvoke会更糟。工作线程充斥着UI消息泵。 BeginInvoke不等到UI线程完成委托的执行。它只是发布请求并快速返回。这就是它似乎挂起的原因。 BeginInvoke发布到UI消息队列的消息不断增加,因为工作线程可能严重超出了UI线程处理它们的能力。

其他评论

我还应该提一下,工作线程在代码中几乎没用。原因是因为您在每次迭代时都会调用InvokeInvoke阻塞,直到UI完成委托的执行。这意味着您的工作线程和UI线程基本上彼此锁定。换句话说,工作人员大部分时间都在等待UI,反之亦然。

<强>解决方案

一种可能的解决方法是降低调用Invoke的速率。而不是在每次循环迭代时调用它,而是每1000次迭代等尝试一次。

任何更好的方法都是不要使用InvokeBeginInvoke。就个人而言,我认为这些用于更新UI的机制被过度使用。让UI线程限制自己的更新速率几乎总是更好,特别是当工作线程进行连续处理时。这意味着您需要在表单上放置一个计时器,并以所需的刷新率打勾。从Tick事件中,您将探测工作线程正在更新的共享数据结构,并使用该信息更新表单上的控件。这有几个好处。

  • 它打破了Control.Invoke强加的UI和工作线程之间的紧密耦合。
  • 它将更新UI线程的责任放在它应该属于的UI线程上。
  • UI线程决定更新的发生时间和频率。
  • UI消息泵没有溢出的风险,就像工作线程启动的编组技术一样。
  • 在继续执行下一步之前,工作线程不必等待确认已执行更新(即,您在UI和工作线程上获得更多吞吐量)。

答案 2 :(得分:0)

首先: 点击Button1已经异步,所以你不需要创建另一个线程来增加,只需调用increment方法抱歉,我是逐行阅读你的问题,当我到达while循环时,我忘记了按钮:

private void Button1_Click(System.Object sender, System.EventArgs e)
{
    Thread t = new Thread(Increment);
    t.IsBackground = true;
    t.Start();
}

第二:如果您确实需要使用线程,那么除非您有充分的理由使用前台线程,否则应始终将线程设置为后台(即foreground prevents your process from terminating)。

第三:如果您要对用户界面进行更新,那么您应该检查InvokeRequired属性并致电BeginInvoke

public void UpdateLabel(string Text)
{

    if (InvokeRequired)
    {
        BeginInvoke(new UpdateLabelDelegate(UpdateLabel), Text);
    }
    else
    {
        Label1.Text = Text;
    }
}

public void Increment()
{
    int i = 0;
    while(true)
    {
        i++; // just incrementing i??
        UpdateLabel(i.ToString());

        Thread.Sleep(1000);// slow down a bit so you can see the updates
    }
}

您还可以“自动化”Invoke Required“模式”:Automating the InvokeRequired code pattern

现在看看你是否还有同样的问题。

我在我的机器上尝试过,它就像一个魅力:

public partial class Form1 : Form
{
    private delegate void UpdateLabelDelegate(string text);
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Thread t = new Thread(Increment);
        t.IsBackground = true;
        t.Start();
    }

    private void UpdateLabel(string text)
    {
        if (label1.InvokeRequired)
        {
            BeginInvoke(new UpdateLabelDelegate(UpdateLabel), text);
        }
        else
        {
            label1.Text = text;
        }

    }

    private void Increment()
    {
        int i = 0;
        while (true)
        {
            i++;
            UpdateLabel(i.ToString());
            Thread.Sleep(1000);
        }
    }
}