首页 > 解决方案 > 从多个线程安全地更新 WinForms 列表控件

问题描述

我有一个 .NET WinForms 应用程序,我需要在其中显示来自同时执行的Thread类的多个实例的消息。消息必须显示在列表控件中(一般情况下为列表框或多列网格)。当用户单击表单的关闭按钮时,线程必须正确终止。

假设我们有一个名为 listBox1 的ListBox控件,它显示来自线程的消息。这是我的问题的表单代码的简化版本:

public partial class Form1 : Form
{
    Thread fThread1, fThread2, fThread3;
    int fMsgNo1, fMsgNo2, fMsgNo3;
    CancellationTokenSource fCTS = new CancellationTokenSource();

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        fThread1 = new Thread(new ParameterizedThreadStart(Thread1Body));
        fThread1.Start(fCTS.Token);

        fThread2 = new Thread(new ParameterizedThreadStart(Thread2Body));
        fThread2.Start(fCTS.Token);

        fThread3 = new Thread(new ParameterizedThreadStart(Thread3Body));
        fThread3.Start(fCTS.Token);

    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        fCTS.Cancel();

        fThread1.Join();
        fThread2.Join();
        fThread3.Join();

        fCTS.Dispose();

    }

    private void Thread1Body(object data)
    {
        CancellationToken token = (CancellationToken)data;

        while (true)
        {
            for (int i = 0; i < 5; i++)
            {
                if (token.IsCancellationRequested)
                    return;
                Thread.Sleep(100);
            }

            fMsgNo1++;
            listBox1.BeginInvoke(
                new AddMessageToListDelegate(AddMessageToList),
                new object[] { $"Thread 1 posted message {fMsgNo1}"});
        }
    }

    private void Thread2Body(object data)
    {
        CancellationToken token = (CancellationToken)data;

        while (true)
        {
            for (int i = 0; i < 10; i++)
            {
                if (token.IsCancellationRequested)
                    return;
                Thread.Sleep(100);
            }

            fMsgNo2++;
            listBox1.BeginInvoke(
                new AddMessageToListDelegate(AddMessageToList),
                new object[] { $"Thread 2 posted message {fMsgNo2}" });
        }
    }

    private void Thread3Body(object data)
    {
        CancellationToken token = (CancellationToken)data;

        while (true)
        {
            for (int i = 0; i < 20; i++)
            {
                if (token.IsCancellationRequested)
                    return;
                Thread.Sleep(100);
            }

            fMsgNo3++;
            listBox1.BeginInvoke(
                new AddMessageToListDelegate(AddMessageToList),
                new object[] { $"Thread 3 posted message {fMsgNo3}" });
        }
    }

    private delegate void AddMessageToListDelegate(string message);

    private void AddMessageToList(string message)
    {
        listBox1.Items.Insert(0, message);
    }

}

看来,这个版本的代码工作正常,但我不完全确定。困扰我的问题是这段代码的上一版本使用Invoke()方法调用而不是BeginInvoke()从工作线程更新UI线程中的列表框,这在用户点击时造成了一种阻塞关闭按钮。我发现在这种情况下,其中一个工作线程可能一直在无休止地等待用 调用的方法的执行结束Invoke。结果,其中一个线程永远不会结束,并且应用程序挂在表单的 Closing 事件处理程序中的Thread.Join()调用之一上。如果我们使用,这个问题也会持续存在Control.BeginInvoke()吗?

如果这个问题根本无法解决这个问题Thread,任何人都可以建议一个更好、更强大的模板来实现我所需要的吗?是的,我知道有诸如BackgroundWorker类之类的东西可用于此任务,但我想知道如何使代码Thread首先为该类正常工作。

标签: .netmultithreadingwinforms.net-corethread-safety

解决方案


推荐阅读