【问题标题】:Why locking 'backgroundworker' when during 'control.Invoke()'为什么在'control.Invoke()'期间锁定'backgroundworker'
【发布时间】:2021-05-25 07:49:57
【问题描述】:

我正在使用“Invoke”方法从“Backgroundworker”更新一个文本框。 但是当我“刷新” ui 中的图片框时,'backgroundworker' 线程会锁定。所以只更新ui是没有问题的。对于此示例,计数器不会递增。谢谢。

int counter = 0;

private delegate void SafeCallDelegate(string text);

public Form1()
{
    InitializeComponent();
    backgroundWorker1.RunWorkerAsync();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    Thread.Sleep(3000); //do stuff
}

private void btnRefresh_Click(object sender, EventArgs e)
{
    pictureBox1.Refresh();
}

private void UpdateTextBox(string text)
{
    if (textBox1.InvokeRequired)
    {
        var d = new SafeCallDelegate(UpdateTextBox);
        textBox1.Invoke(d, new object[] { text });
    }
    else
    {
        textBox1.Text = text;
    }
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (true)
    {
        Thread.Sleep(100);
        counter++;
        UpdateTextBox(counter.ToString());
    }
}

【问题讨论】:

  • BackgroundWorkers 不再像 c# 具有良好的 async/await 支持那样普遍。你真的需要 BackgroundWorkers 吗?
  • 仅供参考:Stephen Cleary (@StephenC) 有一篇很棒的“Task.Run vs BackgroundWorker”文章:blog.stephencleary.com/2013/05/…
  • 事实上,乍一看,我推荐System.Windows.Forms.Timer“实现一个定时器,它以用户定义的时间间隔引发一个事件。这个定时器针对在 Windows 窗体中的使用进行了优化应用程序,并且必须在窗口中使用。”

标签: c# .net winforms


【解决方案1】:

Control.Invoke 会一直阻塞,直到主线程上的调用完成,并且由于绘画事件中的 3s 睡眠,这将需要一段时间。如果您不希望这样做,请使用 .BeginInvoke,它会将更新发布到主线程并立即返回。

但是,cmets 是正确的,因为 async/await/Task.Run 或者计时器是更好的选择。

【讨论】:

    【解决方案2】:

    原因是因为Invoke的意思是等待主UI线程做完工作,后台线程才能继续。

    调用是使用消息完成的,就像绘画一样。

    当后台线程调用 Invoke 时,一条消息被放入主 UI 线程正在处理的消息队列中,当它收到该消息时,无论您传递的委托做什么,都将在主 UI 的上下文中完成线。在处理消息时,后台线程将等待 Invoke 返回,这只会在处理完消息后发生。

    现在,如果您在图片框的绘制中执行 Thread.Sleep(3000) 会发生什么?绘制也是使用消息完成的,消息循环代码看起来简化如下:

    while (true)
    {
        var message = WaitForAndGetNextMessage();
        ProcessMessage(message); // this will return only when message has been processed
    }
    

    因此,当图片框的绘制消息到达,并且您在其中休眠 3 秒时,消息循环不会处理消息。如果队列中有来自 Invoke 方法的消息,这也会延迟相同的 3 秒,而在延迟的同时,您的后台线程正在等待消息被处理,这意味着它也将被卡住等待3 秒。

    “解决”此问题的一种方法是使用即发即弃的BeginInvoke 而不是Invoke。这会将消息放入队列中,但后台线程不会等待它被处理,而是会立即继续。当然,这意味着您的 100 毫秒后台循环将在“绘制”油漆盒时将大约 30 条消息添加到队列中,并且所有这 30 条消息都将在很短的时间内得到处理。


    其他人在 cmets 中暗示的任务会解决这个问题吗?如果您仍然在事件处理程序中执行冗长的操作(例如 Thread.Sleep),则不会。

    【讨论】:

      【解决方案3】:

      您应该使用 Microsoft 的响应式框架(又名 Rx)- NuGet System.Reactive.Windows.Forms 并添加 using System.Reactive.Linq; - 然后您可以这样做:

      public Form1()
      {
          InitializeComponent();
          
          Observable
              .Interval(TimeSpan.FromMilliseconds(100.0))
              .ObserveOn(this)
              .Subscribe(n => textBox1.Text = n.ToString());
      }
      

      就是这样。这是一个每 100 毫秒触发一次的计时器,它将调用推送到当前表单,然后更新文本框。

      它比后台工作线程或普通线程干净得多。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-02
        • 1970-01-01
        • 1970-01-01
        • 2012-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多