如何在单个线程上异步执行慢速方法?

时间:2014-10-13 17:49:57

标签: c# .net performance asynchronous

假设我在WindowsFormsApplication中有以下基本代码。

ButtonClick(object sender, System.EventArgs e)
{
    Stopwatch SW = new Stopwatch();
    SW.Start();
    WriteToGui("Hello World");
    textBox1.Text += String.Format("Updated in {0} ms.{1}",
        SW.Elapsed.TotalMilliseconds,
        System.Environment.NewLine);
}

WriteToTheGUI(string newMessage)
{
     textBox1.Text += String.Format("{0}{1}", newMessage, System.Environment.NewLine);
}

我会得到一个看起来像这样的文本框:

Hello World
Updated in 0.5000 ms

我希望输出实际上是:

Updated in 0.0005 ms
Hello World

编辑:取出所有以前的额外信息。

文本框控件本身确实有一个可以使用BeginInvoke调用的消息泵。

如何将所提供呼叫的测试更新部分替换为将更新添加到文本框消息泵并稍后再处理的呼叫?

编辑3: 有太多关注UI本身,而不是手头的任务,所以我会尝试解释目标。

我有一个外部第三方数据库记录和处理一些专门的消息传递。

我注意到基本发送消息命令大约需要0.5毫秒,我相信它等待来自服务器的回复它收到消息。基础消息以突发形式发生,因此我可以拥有数千个或更多它们来快速记录。因此,代码可以变得非常备份。  这些消息按时间显示:

[        ][        ][        ][       ][       ]  and so on and the time keeps growing with more messages.

每个括号表示从每个消息调用返回的时间。 我希望它是:

[        ]
  [        ]
    [        ]   and so on so I am done with them quickly and waiting for replies.

软件和数据库可以(据说)处理我想要实现的更高的消息速率。无论哪种方式,我都会尽我所能尽力等待。 该软件要求从创建连接的线程发送任何更新,或使用BeginInvoke正确发送消息。这就是为什么我以UI为例。

我不需要等待数据库中的任何响应或错误,因为它是单独处理的,我只需要尽快发送消息并等待回复。

我可以看到两种方法,对于每条消息,为每条消息创建一个新线程,这将留下数以千计的线程或使用有限线程的线程池,这将再次变慢,因为它最终会坐下来并在发送更多消息之前等待。

但我认为没有必要创建无数个线程。我应该能够使用BeginInvoke将消息添加到消息泵,即使我不需要。愚蠢的部分是我不知道如何在同一个线程上做到这一点。我只需要任何函数方法的强制BeginInvoke的语法。 然后我可以自由地做我喜欢的事情,并且只受到消息泵处理速度的限制。

更新

在这里查看帖子:How to update the GUI from another thread in C#? 将WriteToGUI中的行更改为:

 textBox1.BeginInvoke(new Action(() => textBox1.Text += String.Format("{0}{1}", newMessage, System.Environment.NewLine)));

之前的实际输出:

Hello World
Updated in .2783 ms.

之后:

Updated in 0.0494 ms.
Hello World

这给出了所需的结果,立即返回并在时间之后打印“Hello World”。 但是,示例中的Action是否启动了新线程,或者这是排队新操作的最佳方法吗?

2 个答案:

答案 0 :(得分:0)

在人类时间尺度上,半毫秒是非常非常短暂的时间段。它不喜欢半秒钟或类似的事情。

您无法从其他主题更新文本框的文字。您只能从UI线程中执行此操作。

如果你有一个长时间运行的非UI操作(目前你有非常快的UI操作)那么你可以明智地考虑寻找替代方案。

答案 1 :(得分:-1)

我不确定为什么代码会明显变慢......即使字符串处理也不应该影响该级别的UI(尽管代码非常糟糕 - 抱歉,老兄)。

对于您正在寻找的内容,您可能会在更新时暂停重绘。

myTextBox.SuspendLayout();
WriteToTheGUI("Hello World");
myTextBox.Text += String.Format({0}{1},newMessage,System.Environment.NewLine());
myTextBox.ResumeLayout();

我很快就会仔细看看它,因为乍一看我不明白为什么会这么慢,我很好奇。但是,如果这有帮助,请告诉我。

一个问题:你的TextBox有多大(多少行)?你是否在某些时候明确了文本然后再加上它?我想象一个带有472行文本的双行文本框,因为你单击按钮236次。

更新:
我不知道你在问什么了。如果我回答主题中的问题,"你如何在一个线程上异步执行某些事情",答案是 - 你不能。根据定义,异步需要多个线程/进程/服务等的交互。


How can I replace the test update portion of the provided call with a call that would add the update to the textbox message pump and only handle it later?

我认为您要求推迟本机Windows事件处理的能力。你可以......但是只有那个"亲爱的上帝,为什么?"一种方式。但是如果你想覆盖消息泵:
http://msdn.microsoft.com/en-us/library/system.windows.forms.message%28v=vs.110%29.aspx

基本上,您正在拦截来自Windows的所有本机消息。但是你来处理这些事件......如果你不这样做,你会遇到像你一样从未见过的问题。

要做你要求的事情,你必须拦截所有消息,将它们存储在某个地方,然后再处理它们;你打开Pandora的盒子,它充满了噩梦以及为什么你永远不想这样做的原因。事实上,如果你让它可靠地工作,我会吃掉我的鼠标。如果没有Tron综合症,暂停布局应该会给你带来同样的效果。事实上,这个:add the update to the textbox message pump and only handle it later正是暂停布局的原因。


你真的有三个选择:

  1. 在您完成
  2. 之前,请不要更新文本框文字
  3. 在您完成
  4. 之前,不要回复更新事件
  5. 多线程
  6. 在不知道WriteToTheGUI()中的代码真正在做什么的情况下,很难提供帮助。在您的示例中,您可以简单地使用StringBuilder来构建文本并将其存储直到长操作完成;这样,您只需在处理结束时设置一次文本框文本。无论这有助于甚至适用于真正的解决方案,我都不知道。


    我不确定你为什么要尝试做你要做的事。无论是什么,我都非常确定你的解决方案比你需要的复杂得多。此外,我并不是要挑剔,但您发布的代码存在不允许编译的问题。如果你没有粘贴你正在运行的实际代码,那么很难提供帮助,而且这里的问题通常都是次要的编程,当你输入时它们不会结转垃圾代码。

    请把它作为建设性的批评......你真的需要放慢速度,退一步,学习.NET开发的基础知识,然后才能进入高级领域。否则,你将巩固复杂(和糟糕)解决方案的思维方式,这将困扰你的职业生涯的其余部分。 .NET旨在使开发人员和#生活简单,最复杂的解决方案是完全错误的。