【问题标题】:Deadlock when worker thread and UI thread calls the same method including invoke ui control工作线程和 UI 线程调用相同方法时死锁,包括调用 ui 控件
【发布时间】:2017-10-17 07:18:20
【问题描述】:

这是我描述问题的代码:

public partial class Form1 : Form
{
    private readonly  object lockObject = new object();
    public Form1()
    {
        InitializeComponent();
    }

    private void btnWorkerThread_Click(object sender, EventArgs e)
    {
        Task.Factory.StartNew(() => SomeLongRunningThread());
    }

    private void SomeLongRunningThread()
    {
        lock (lockObject)
        {
            Thread.Sleep(3000);
            if (txtResult.InvokeRequired)
            {
                Thread.Sleep(3000);
                txtResult.Invoke((MethodInvoker) delegate { txtResult.Text = DateTime.Now.ToShortTimeString(); });
            }
            else
            {
                Thread.Sleep(3000);
                txtResult.Text = DateTime.Now.ToShortTimeString();
            }
        }

    }

    private void btnUIThread_Click(object sender, EventArgs e)
    {
        SomeLongRunningThread();
    }
}

当工作线程启动然后btnUIThread被点击时发生死锁:

txtResult.InvokeRequried 上好像发生了死锁:

  • 工作线程将控制权传递给 ui 线程
  • 由于 ui 线程轮到,它同时更新 txtResult 并尝试启动 btnUIThread 点击。
  • 在线txtResult.Invoke((MethodInvoker) delegate { txtResult.Text = DateTime.Now.ToShortTimeString(); }); 发生死锁

如果我错了,请纠正我。

问题是如何处理死锁?

编辑:经过各位大侠的宝贵解答,我的实际项目比预期的要复杂,锁块有更多的代码,我应该保证一次一个线程执行。

【问题讨论】:

  • 这是一个经过验证的示例,那么仅使用该代码会发生这种情况吗?
  • 是的,它是@RandRandom
  • 独立逻辑,在UI线程上从UI控件中读取需要的数据,传递给工作线程,工作完成后返回返回计算数据,用返回的数据更新UI控件
  • 这是复制粘贴错误吗? txtResult.Text = txtResult.Text = DateTime.Now.ToShortTimeString();
  • @RandRandom 已更正。谢谢

标签: c# multithreading winforms


【解决方案1】:

为避免死锁,您应该以不同的方式lock。锁定所有线程代码是没有意义的。 此外,此锁 (lockobject) 不是必需的,因为在您的示例中 SomeAction() 已经同步以在主/UI 线程中执行。

private void SomeLongRunningThread()
{

    Thread.Sleep(3000);
    if (txtResult.InvokeRequired)
    {
        Thread.Sleep(3000);
        txtResult.Invoke((MethodInvoker) delegate { SomeAction });
    }
    else
    {
        Thread.Sleep(3000);
        SomeAction();
    }


}

private void SomeAction(){
    // This lock is not needed as long this method is only called from SomeLongRunningThread()
    //lock (lockObject)
    //{
        txtResult.Text = DateTime.Now.ToShortTimeString();
    //}
}

【讨论】:

  • 看来有帮助
  • 如果SomeAction();已经同步到主/UI线程中执行,如你所说,为什么我需要将SomeAction();的代码包装成锁?据我了解,一次只有 1 个线程(主线程)会执行 SomeAction();,那么要锁定什么?
  • 现在this comment 更有意义
  • @asdf_enel_hak 是的,我在 10 分钟前编辑了答案,就这么说。我将从我的答案中完全删除该锁定以避免混淆。
  • // This lock is not needed as long this method is only called from SomeLongRunningThread() 的评论并不完全正确。它只在 UI 线程上调用,因此不需要锁。
【解决方案2】:

这里根本不需要lock

调用 txtResult.Invoke((MethodInvoker) delegate { txtResult.Text = DateTime.Now.ToShortTimeString(); }); 会将委托推送到 UI 消息循环队列。当循环准备好时,它会弹出委托并执行。 UI 一次只能运行一件事 - 没有竞争,也没有死锁。

【讨论】:

    【解决方案3】:

    .NET 4.5 引入了IProgress< T> 接口来报告线程和任务的进度,以及Progress< T> 实现在创建它的线程中运行回调或引发事件。有了这个和async/await,您就不需要调用BeginInvoke 或`Invoke。

    这段代码可以使用Progress来简化:

    public partial class Form1 : Form
    {    
        IProgress<string> _progress;
        public Form1()
        {
            InitializeComponent();
            _progress = new Progress<string>(UpdateUI);
        }
    
        void UpdateUI(string message)
        {
            txtResult.Text = message;
        }
    
        private void btnWorkerThread_Click(object sender, EventArgs e)
        {
            Task.Run(() => SomeLongRunningThread());
        }
    
        private void SomeLongRunningThread()
        {
            Thread.Sleep(3000);
            _progress.Report(DateTime.Now.ToShortTimeString());
        }
    
        private void btnUIThread_Click(object sender, EventArgs e)
        {
            SomeLongRunningThread();
        }
    }
    

    【讨论】:

    • 您的回答似乎与问题无关。
    • @KostyaK 相反,它完全删除了它。在当前 .NET 版本中无需使用 InvokeBeginInvoke 或任何类型的锁定。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    • 2012-12-15
    • 2012-01-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多