工作线程和UI线程调用相同方法时的死锁,包括调用ui控件

时间:2017-10-17 07:18:21

标签: c# multithreading winforms

以下是我的代码描述的问题:

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(); });
  • 发生死锁

如果我错了,请纠正我。

问题是如何处理死锁?

编辑:经过研究员的有价值的答案后,我的实际项目比预期的要复杂得多,并且锁定块有更多代码,我应该保证一次只能由一个线程执行。

3 个答案:

答案 0 :(得分:3)

为了避免死锁,您应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();
    //}
}

答案 1 :(得分:3)

此处无需lock

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

答案 2 :(得分:2)

.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();
    }
}