C#使用调用线程,冻结表单

时间:2013-01-28 19:19:14

标签: c# multithreading invoke

我尝试使用线程并防止程序在线程繁忙时冻结。它应该显示进度(写出0' s / 1' s)而不仅仅是在完成后显示结果,同时冻结表格。

在当前的程序中,我试图写入文本框,实际上看到不断进展,并且表单不会受到其他线程任务的影响。

我现在拥有的是可以使用调用写入带有线程的文本框,但它只显示结果(表单在线程繁忙时冻结),并且表单冻结。

表格图片:

enter image description here

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace MultiThreading
{
public partial class MultiThreading : Form
{
    public MultiThreading()
    {
        InitializeComponent();
    }

    Thread writeOne, writeTwo;

    private void writeText(TextBox textBox, string text)
    {
        if (textBox.InvokeRequired)
        {
            textBox.BeginInvoke((MethodInvoker)delegate()
            {
                for (int i = 0; i < 500; i++)
                {
                    textBox.Text += text;
                }
            });
        }
        else
        {
            for (int i = 0; i < 500; i++)
            {
                textBox.Text += text;
            }
        }
    }
    private void btnWrite1_Click(object sender, EventArgs e)
    {
        writeOne = new Thread(() => writeText(txtOutput1, "0"));
        writeOne.Start();
    }

    private void btnWrite2_Click(object sender, EventArgs e)
    {
        writeTwo = new Thread(() => writeText(txtOutput2, "1"));
        writeTwo.Start();
    }

    private void btnClear1_Click(object sender, EventArgs e)
    {
        txtOutput1.Clear();
    }

    private void btnClear2_Click(object sender, EventArgs e)
    {
        txtOutput2.Clear();
    }

    private void btnWriteBoth_Click(object sender, EventArgs e)
    {
        writeOne = new Thread(() => writeText(txtOutput1, "0"));
        writeTwo = new Thread(() => writeText(txtOutput2, "1"));

        writeOne.Start();
        writeTwo.Start();
    }

    private void btnClearBoth_Click(object sender, EventArgs e)
    {
        txtOutput1.Clear();
        txtOutput2.Clear();
    }
}

}

编辑:

对于任何想知道的人来说,我是多线程的新手,我只是想写一个小程序来了解最好的方法。

我知道我以前的调用并没有真正帮助,因为我仍然没有给表单提供更新的机会,所以它到了那里。

好的,这样运行1个这样的线程,但仍然在一起运行多个线程,不会在线程完成之后更新表单。
我已经添加了一个thread.sleep(),所以我可以在写作时尝试清除,看看我是否仍然可以使用该表单。

写入1个文本框时,我仍然可以在写入时清除屏幕 但是一旦我使用2个线程,我就不能再使用该表单直到线程完成,并给出输出。

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 500; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(2);
            }));
        }

    }

(如果我对此完全错误,我不介意阅读一些示例/主题,我仍然试图看看除此之外最好的方法是什么BackgroundWorker的)

编辑2:

我通过减少写入量来减少调用次数,但是增加延迟会产生相同的持续写入效果,只是减少了负载。

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 500; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(2);
            }));
        }

    }

编辑3:

Sumeet的例子使用

  

Application.DoEvents();

(注意s,.DoEvent不起作用,可能是错字:P),同时写多个字符串&amp;让他们显示进度而不仅仅是结果。

所以代码再次更新:)

*使用新按钮创建5个线程,将随机数写入两个文本框

private void writeText(TextBox textBox, string text)
    {
        for (int i = 0; i < 57; i++)
        {
            Invoke(new MethodInvoker(() =>
            {
                textBox.Text += text;
                Thread.Sleep(5);
                Application.DoEvents();
            }));
        }

    }
private void btnNewThread_Click(object sender, EventArgs e)
    {
        Random random = new Random();
        int[] randomNumber = new int[5];
        for (int i = 0; i < 5; i++)
        {
            randomNumber[i] = random.Next(2, 9);
            new Thread(() => writeText(txtOutput1, randomNumber[i-1].ToString())).Start();
            new Thread(() => writeText(txtOutput2, randomNumber[i-1].ToString())).Start();
        }
    }

6 个答案:

答案 0 :(得分:7)

此解决方案有效!已经检查过了。

问题是你一直告诉UI线程改变文本,但永远不要让它有时间向你显示更新的文本。 要让您的用户界面显示已更改的文字,请添加Application.DoEvents行,如下所示:

textBox.Text += text;
Application.DoEvents();

P.S。 :删除你的If / Else循环的else块,它是多余的,并且如其他人所指出的那样,没有任何使用创建这两个线程,因为它们正在做的就是在UI线程本身上发布消息。

答案 1 :(得分:5)

您仍在执行单线程任务,只需在UI线程上重新启动它。

for (int i = 0; i < 500; i++){
    string text = ""+i;
    textBox.BeginInvoke((MethodInvoker)delegate()
            {
                textBox.Text += text;
            });
}

答案 2 :(得分:3)

问题在于你正在启动一个新线程,然后新线程除了为UI线程添加一个新任务以进行大量工作外什么都不做。为了保持您的表单响应,您需要有时间在UI线程什么都不做,或者至少不花费大量时间执行任何任务。

为了保持表单的响应,我们需要进行大量的BeginInvoke(或Invoke}次调用。

private void writeText(TextBox textBox, string text)
{
    for (int i = 0; i < 500; i++)
    {
        Invoke(new MethodInvoker(() =>
        {
            textBox.Text += text;
        }));
    }
}

通过进行大量的小调用调用,可以在操作过程中处理绘制事件,鼠标移动/点击事件等事件。另请注意,我删除了InvokeRequired电话。我们知道将从非UI线程调用此方法,因此不需要它。

答案 3 :(得分:2)

你正在打败使用线程的目的。

您的所有主题都是告诉 UI线程使用BeginInvoke()执行某些代码。

所有实际工作都在UI线程上进行。

答案 4 :(得分:0)

您正在进行数据处理,或者您只是想尝试为UI设置动画。

对于数据处理,您应该在后台线程上完成所有繁重工作,并且只偶尔更新UI。在您的示例中,TextBox在这方面特别麻烦,因为您将数据添加到底层数据模型数百次,并且UI元素(TextBox)每次渲染所需的时间更长。 您必须注意更新UI的频率,以便UI更新的处理不会压倒数据模型更新。 TextBoxes很讨厌。

在下面的示例中,在paint事件期间设置的标志确保在TextBox完成绘制上次更新之前,其他UI更新不会排队:

string str = string.Empty;
public void DoStuff()
{
    System.Threading.ThreadPool.QueueUserWorkItem(WorkerThread);
}

void WorkerThread(object unused)
{
    for (int i = 0; i < 1000; i++)
    {
        str += "0";
        if (updatedUI)
        {
            updatedUI = false;
            BeginInvoke(new Action<string>(UpdateUI), str);
        }
    }
    BeginInvoke(new Action<string>(UpdateUI), str);
}

private volatile bool updatedUI = true;
void textbox1_Paint(object sender, PaintEventArgs e) // event hooked up in Form constructor
{
    updatedUI = true;
}

void UpdateUI(string str)
{
    textBox1.Text = str;
}

另一方面,如果UI动画是你的目标,那么你可能应该使用TextBox以外的东西。它不是为了经常处理更新而设计的。对于特定用例,可能会对文本呈现进行一些优化。

答案 5 :(得分:0)

您绝不能在高容量应用程序中使用字符串。 UI与否。是否多线程。

您应该使用StringBuilder来累积字符串。然后分配

tb.Text = sb.ToString();