从线程 C# 更新 GUI,而不绑定到 UI 控件

Updating GUI from thread C#, without binding to UI controls

据了解,当您需要从其他线程更新gui 时,会使用Invoke 方法。但是如何在不将控件绑定到代码的情况下实现它?

这是我的测试 class:

class test
{
   public List<Thread> threads = new List<Thread>();
    public int nThreads = 0;
    public int maxThreads = 5;

    public void DoWork(object data)
    {
        string message = (string)data;
        //MessageBox.Show(message);        


    }

    public void CreateThread(object data)
    {
        if (nThreads >= maxThreads)
            return;
        Thread newThread = new Thread(DoWork);
        threads.Add(newThread);
        newThread.IsBackground = true;
        newThread.Start(data);
        nThreads++;


    }

    public void WindUpThreads()
    {
        //MessageBox.Show("count: " + nThreads.ToString());
        for(int i = 0; i < threads.Count; i++)
        {
            if (threads[i].IsAlive == false)
            {
                threads[i].Abort();
                threads.RemoveAt(i);
               //MessageBox.Show("removing at " + i.ToString());
            }

        }

        nThreads = threads.Count;
    }

}

问题是 = 我必须使用什么技术来更新 gui 而不是将控制硬编码到 class?我试图将委托传递给 DoWork 方法,但这不起作用 (http://pastebin.com/VaSYFxPw)。谢谢!

我正在使用 WinForms、.NET 3.5

这是 button_click 处理程序:

 private void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        test thTest = new test();
        string[] strings;
        try
        {

            strings = File.ReadAllLines("C:\users\alex\desktop\test.txt");
        }

        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
            return;
        }

        bool flag = true;
        int counter = 0;
        int dataCount = strings.Length;

        while (flag == true)
        {
            if (counter >= dataCount)
            {
                flag = false;
            }

            while (thTest.nThreads < thTest.maxThreads)
            {
                if (flag == false)
                    break;

                thTest.CreateThread(strings[counter]);
                //Data d = new Data();
                //d.deleg = AddItem;
                //d.mess = strings[counter];
                //thTest.CreateThread((object)d);
                //MessageBox.Show(counter.ToString());
                counter++;
            }

            thTest.WindUpThreads();

            if (flag == false)
            {
                do
                {
                    thTest.WindUpThreads();

                } while (thTest.nThreads != 0);
            }



        }

        listBox1.Items.Add("Done");

    }

我的想法是为我要处理的每个任务启动线程。在我检查是否有已完成的任务后,它们将被关闭并启动新任务,直到没有更多任务为止。

与其让 DoWork 负责用它执行的操作结果更新 UI,不如让它 return 的值:

//TODO change the type of the result as appropriate
public string DoWork(string message)
{
    string output = "output";
    //TODO do some work to come up with the result;
    return output;
}

然后使用 Task.Run 创建一个 Task 来表示正在线程池线程中完成的工作。然后,您可以从按钮单击处理程序中等待该任务。

private async void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    test thTest = new test();
    //I'd note that you really should pull out reading in this file from your UI code; 
    //it should be in a separate method, and it should also be reading 
    //the file asynchronously.
    string[] strings;
    try
    {
        strings = System.IO.File.ReadAllLines("C:\users\alex\desktop\test.txt");
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        return;
    }
    foreach (var line in strings)
    {
        var result = await thTest.DoWork(line);
        listBox1.Items.Add(result);
    }
    listBox1.Items.Add("Done");
}

如果您真的想守旧派,可以改用 BackgroundWorker。只需在 DoWork 处理程序中完成您的工作,在计算结果后设置结果(通过参数),并在 RunWorkerCompleted 事件处理程序中用结果更新 UI。这使您可以将 UI 和 non-UI 分开工作,尽管它的功能、通用性和可扩展性远不如新功能。

The question is = what tecnique I must use in order to update gui but not hardcode control into class? I've tried to pass delegate to DoWork Method, but this doesn't work

这确实是可能的技术之一。它不起作用,因为您在 UI 线程中有一个阻塞循环 - button1_Click 处理程序中的大部分代码。产生额外的工作线程并不重要 - 该代码使 UI 线程忙碌,因此 Control.Invoke / Control.BeginInvoke 不起作用,因为它们由 [=19= 处理] 线程消息循环,在这种情况下没有机会这样做。最终结果是经典死锁。

因此,您可以使用委托方法,但要使其正常工作,您需要将该代码移到单独的线程中。像这样

private void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    var worker = new Thread(DoWork);
    worker.IsBackground = true;
    worker.Start();
}

private void OnWorkComplete(Exception error)
{
    if (error != null)
        MessageBox.Show(error.Message);
    button1.Enabled = true;
}

private void DoWork()
{
    Exception error = null;
    try { DoWorkCore(); }
    catch (Exception ex) { error = ex; }
    Invoke(new Action(OnWorkComplete), error);
}

private void DoWorkCore()
{
    test thTest = new test();
    // NOTE: No try/catch for showing message boxes, this is running on a non UI thread
    string[] strings = File.ReadAllLines("C:\users\alex\desktop\test.txt");

    bool flag = true;
    int counter = 0;
    int dataCount = strings.Length;
    // The rest of the code...
    // Pass a delegate to the other threads.
    // Make sure using Invoke when you need to access/update UI elements
}