Multithreading mono挂起而MS.Net不挂起的线程问题';T

Multithreading mono挂起而MS.Net不挂起的线程问题';T,multithreading,mono,invoke,begininvoke,Multithreading,Mono,Invoke,Begininvoke,我正在用mono测试我的应用程序,以防Linux端口出现问题。我最初考虑在这里粘贴3000行代码,但最后我设计了一个小示例;) 您有一个带有按钮的表单(诗意地命名为Button1),还有一个标签(毫无疑问,标签上有名称Label1)。所有人都在一种叫做Form1的形式上过着幸福的生活。单击按钮1启动一个无限循环,该循环递增本地计数器,并更新标签1(使用调用)以反映其值 现在在Mono中,如果调整表单大小,标签将停止更新,永远不会重新启动。MS实现不会出现这种情况BeginInvoke没有更好的效

我正在用mono测试我的应用程序,以防Linux端口出现问题。我最初考虑在这里粘贴3000行代码,但最后我设计了一个小示例;)

您有一个带有按钮的表单(诗意地命名为
Button1
),还有一个标签(毫无疑问,标签上有名称
Label1
)。所有人都在一种叫做
Form1
的形式上过着幸福的生活。单击
按钮1
启动一个无限循环,该循环递增本地计数器,并更新
标签1
(使用
调用
)以反映其值

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

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


编辑: 迄今取得的一些进展:

  • 调用BeginInvoke实际上起作用;只是,UI刷新速度不够快,所以它似乎停止了
  • 在mono上,当您在UI队列中插入消息时(例如通过调整表单大小),整个线程将挂起。事实上,同步
    Invoke
    调用永远不会返回。我想知道为什么
  • 有趣的是:即使使用
    BeginInvoke
    ,异步调用也不会在调整大小操作结束之前执行。在MS.Net上,它们在调整大小的同时保持运行

代码如下所示(C版本更低):

或者,在C#中


首先也是最重要的:点击Button1已经是异步的,所以你不需要创建另一个线程来递增,只需调用递增方法。对不起,我正在逐行阅读你的问题,当我进入while循环时,我忘记了按钮:

private void Button1_Click(System.Object sender, System.EventArgs e)
{
    Thread t = new Thread(Increment);
    t.IsBackground = true;
    t.Start();
}
第二:如果您确实需要使用线程,那么您应该始终将线程设置为背景(即),除非您有充分的理由使用前景线程

Third:如果要更新UI,则应检查
invokererequired
属性并调用
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
    }
}
您还可以“自动”调用所需的“模式”:

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

我在我的机器上试过,效果很好:

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);
        }
    }
}
你知道这种差异在哪里吗 来自哪里?你将如何解决它

我不确定。我在你的代码中没有看到任何明显的东西会导致Mono和.NET之间的差异。如果非要我胡乱猜测的话,我会说你有可能在Mono中偶然发现了一个模糊的bug。不过,我认为Mono可能会使用一种完全不同的机制来处理WM_PAINT消息,从而导致表单刷新。重复调用
Invoke
可能会破坏Mono刷新表单的能力,从而不断地重击UI线程

最后,为什么不开始思考 在这里工作

在紧循环中调用
Invoke
已经够糟糕的了,但是
BeginInvoke
会更糟。工作线程正在淹没UI消息泵
BeginInvoke
不会等待UI线程完成委托的执行。它只是发布请求并快速返回。这就是为什么它看起来很悬。
BeginInvoke
发布到UI消息队列的消息不断增加,因为工作线程可能严重超出UI线程处理这些消息的能力

其他评论

我还应该提到,工作线程在代码中几乎是无用的。原因是您在每次迭代中都要调用
Invoke
<代码>调用阻塞,直到UI完成委托的执行。这意味着您的工作线程和UI线程基本上是同步的。换句话说,工作者大部分时间都在等待UI,反之亦然

解决方案

一种可能的修复方法是降低调用
Invoke
的速度。不要在每次循环迭代时调用它,而是尝试每1000次迭代或类似的操作一次

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

  • 它打破了UI和
    Control.Invoke
    强加的工作线程之间的紧密耦合
  • 它将更新UI线程的责任放在它应该属于的UI线程上
  • UI线程可以指定更新的时间和频率
  • UI消息泵不会像工作线程启动的封送处理技术那样溢出
  • 工作线程不必等待更新已执行的确认,然后再继续执行下一步(即,在UI和工作线程上都可以获得更高的吞吐量)

    • 这是mono运行时的一个bug,至少我认为是这样。这段代码可能不是一个好的实践(我不是线程专家),但表明一个bug的事实是windows和Linux上的行为不同

      在Linux上,mono具有完全相同的行为
      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
          }
      }
      
      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);
              }
          }
      }