【问题标题】:Running two tasks simultaneously同时运行两个任务
【发布时间】:2021-09-20 19:00:11
【问题描述】:

嗨,我有一个 winform 应用程序,它由 25 个文本框组成,这些文本框代表实时接收的值,而它们下方有标签,显示这些值的最小值和最大值flow in shown with red arrows

现在,当应用程序启动时,它会在每个文本框及其标签中填充数据,延迟为 1 秒。因此,它需要 7 到 8 秒才能完全填充数据。

我想要的是将填充文本框和标签的任务分成两部分。

我尝试了多线程,但它不适合我

到目前为止我的代码

        Thread mmathread;
        while (start)
        {
            try
            {
                RPM_TEXT.Text = ReadRPM().ToString() + " rpm";
                mmathread = new Thread(() => mma(rpm_list, ReadRPM(), RPM_mma));
                mmathread.Start();
                
            }
            catch { }

            try { EL_TEXT.Text = ReadEngineLoad().ToString() + " %";
                mmathread = new Thread(() => mma(el_list, ReadEngineLoad(), EL_mma));
                mmathread.Start();
                
            }
            catch { }
            try { ECT_TEXT.Text = ReadCoolantTemp().ToString() + " °C";
                mmathread = new Thread(() => mma(ect_list, ReadCoolantTemp(), ECT_mma));
                mmathread.Start();
                }
            catch { }
            }

函数定义为

private void mma(List<int> parameter_list, int parameter, Label label)
            {
            if (parameter_list.Count == 9) { parameter_list.Add(parameter); 
            parameter_list.RemoveAt(0); }
            else { parameter_list.Add(parameter); }
            minpara = parameter_list.Min();
            maxpara = parameter_list.Max();
            avrgpara = parameter_list.Sum() / parameter_list.Count;

            label.Text = $"Min = {minpara}, Avg = {avrgpara}, Max = {maxpara}";
            }

如果我直接调用该函数,它会更新标签,但如果我向它添加线程,则标签不会填充

【问题讨论】:

  • 空的try..catches 通常不好。您不应该使用非 GUI 线程来触摸 GUI 控件。需要调用这些调用。
  • 您不能从非 UI 线程直接访问 UI 元素(任何 UI 元素,以任何方式)。如果您尝试,它可能会失败,您的应用程序可能会挂起,或者您可能会遇到异常(这取决于)。在 Windows 窗体中,所有控件(包括窗体(从 Control 继承)都有一个属性 InvokeRequired,如果您在非 UI 线程上,则该属性为 true,以及一个方法 (Invoke) 将封送您的调用到 UI 线程。看看@BojanB 和我对传统方式(在 WinForms 中)使用它的回答。
  • @adnan:你问了一个问题。你得到了三个答案。您对其中任何一个有任何疑问吗?他们中的任何一个有帮助吗?其中之一是否完美地回答了您的问题?我们付出了一些努力来帮助您。一些反馈,任何反馈,都会很有用

标签: c# multithreading winforms


【解决方案1】:

使用线程时,您需要在标签上使用 Invoke。像这样

 label.Invoke((MethodInvoker) delegate () {label.Text = $"Min = {minpara}, Avg = {avrgpara}, Max = {maxpara}"};

https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.control.invoke?view=net-5.0

【讨论】:

  • 我试过了,但这使得更新标签的速度比我手动更新时更慢
【解决方案2】:

您可以使用Task&lt;T&gt;s 代替Threads¹。 Task.Run 方法将提供的操作卸载到ThreadPool,稍后可以通过Task&lt;T&gt;Result 属性获得操作的结果。您可以awaitTask&lt;T&gt;s,以便在后台运行操作时 UI 将保持响应。您还可以一次启动多个任务,以便并行调用这些操作。在这种情况下,您可以使用Task.WhenAll 方法await 所有并行任务。示例:

while (start)
{
    Task delayTask = Task.Delay(TimeSpan.FromSeconds(1));
    Task<string> task1 = Task.Run(() => ReadRPM());
    Task<string> task2 = Task.Run(() => ReadEngineLoad());
    Task<string> task3 = Task.Run(() => ReadCoolantTemp());
    await Task.WhenAll(task1, task2, task3);
    RPM_TEXT.Text = task1.Result;
    EL_TEXT.Text = task2.Result;
    ECT_TEXT.Text = task3.Result;
    await delayTask;
}

在此示例中,我添加了一个 Task.Delay 任务,以将最大频率设置为每秒一个循环。

此代码必须放在具有async 修饰符的方法(或事件处理程序)中,以启用await 运算符。

¹ 我指的是Thread 类,而不是thread as a concept

【讨论】:

  • Task.Run 将在后台线程上运行指定的工作毫无价值。您没有使用 "Task&lt;T&gt;s 而不是线程",而是使用它们来表示线程。在这种情况下,您正在安排三个线程上的工作,等待所有三个线程上的工作完成 (WhenAll),然后更新 UI。这将起作用,并且可能是 OP 想要的。但是,通常情况下,当您使用实时系统时,您会使用定期将数据编组到 UI 线程的长寿命线程。
  • @Flydog57 使用长寿命线程比使用ThreadPool 线程读取系统信息有什么优势?
  • 嗯,这取决于。我工作过的许多控制系统在控制域(在自己的线程中运行,具有自己严格的时间要求)和 UI 域(响应性是关键,并且相差 250 -500 毫秒不是问题)。这实际上取决于 OP 希望他的系统如何运行。假设它满足 OP 的需求,您的解决方案没有任何问题。我只是指出您正在使用线程池线程,并且您要等到它们都完成后才更新 UI。
  • @Flydog57 我能想到的使用ThreadPool 的一个缺点是,如果程序定期向ThreadPool 安排更多工作,池可能会饱和,并且UI 可能不会用新值更新几秒钟(不会变得无响应)。不过这将是一个临时问题,因为ThreadPool 很快就会向池中注入更多线程(大约每秒一个新线程),并且可用线程就足够了。
【解决方案3】:

详细说明@BojanB 的答案,您可以使用传统的WinForms InvokeRequired/Invoke 模式。如果在非 UI 线程上评估 InvokeRequired 属性,则返回 true。如果需要Invoke 调用,那么您只需Invoke 相同的调用(将调用编组到 UI 线程上)就可以完成。如果您这样做,则可以从 UI 线程或任何其他线程调用相同的函数。

首先我创建了这三个辅助函数:

private void SetTextBoxFromThread(TextBox textBox, string contents)
{
    if (InvokeRequired)
    {
        Invoke(new Action<TextBox, string>(SetTextBoxFromThread), new object[] { textBox, contents });
        return;
    }

    textBox.Text = contents;
}

private string ReadTextBoxFromThread(TextBox textBox)
{
    if (InvokeRequired)
    {
        return (string)Invoke(new Func<TextBox, string>(ReadTextBoxFromThread), new[] { textBox });
    }

    return textBox.Text;
}
private void SetLabelFromThread(Label label, string contents)
{
    if (InvokeRequired)
    {
        Invoke(new Action<Label, string>(SetLabelFromThread), new object[] { label, contents });
        return;
    }

    label.Text = contents;
}

有了这些,我使用这个愚蠢的小线程函数(匹配 ThreadStart 委托的 async 版本)对它们进行了测试:

private int isPaused = 0;       //used like a bool, int for clear atomicity

private async void ThreadFunc()
{
    while (isPaused == 0)
    {
        var now = DateTime.Now;
        var str1 = now.ToString("T");
        var str2 = now.ToString("t");
        var str3 = now.ToString("s");

        SetTextBoxFromThread(textBox1, str1);
        SetTextBoxFromThread(textBox2, str2);
        SetTextBoxFromThread(textBox3, str3);

        await Task.Delay(1500);

        SetLabelFromThread(label1, ReadTextBoxFromThread(textBox1));
        SetLabelFromThread(label2, ReadTextBoxFromThread(textBox2));
        SetLabelFromThread(label3, ReadTextBoxFromThread(textBox3));
    }
}

【讨论】:

  • 对于它的价值,我在一个长期存在的线程中测试了它(因为它很容易做到)。从在线程池线程上运行的短期工作负载中运行这些方法(SetTextBoxFromThread 和朋友)将以相同的方式工作。它们简单地提供了一种方法来设置(或读取)文本框中的文本或来自任何线程的标签。
猜你喜欢
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-23
  • 2012-08-24
相关资源
最近更新 更多