我目前正在用C#写我的第一个程序,我对这种语言非常陌生(到目前为止只使用C)。我做了很多研究,但所有的答案都太笼统了,我根本无法让它工作。
所以这里我的(非常常见的)问题:我有一个WPF应用程序,它从用户填写的几个文本框中获取输入,然后用它来做大量的计算。它们应该需要大约2-3分钟,所以我想更新一个进度条和一个文本块,告诉我当前的状态是什么。而且我需要存储用户的UI输入,并将它们提供给线程,所以我有第三个类,我用它来创建一个对象,并想把这个对象传递给后台线程。显然,我会在另一个线程中运行计算,这样UI就不会冻结,但我不知道如何更新UI,因为所有的计算方法都是另一个类的一部分。经过大量的研究,我认为最好的方法是使用调度员和第三方物流,而不是后台工作人员,但老实说,我不确定他们如何工作,经过大约20个小时的试错与其他答案,我决定自己问一个问题。
下面是我的一个结构非常简单的程序:
public partial class MainWindow : Window
{
public MainWindow()
{
Initialize Component();
}
private void startCalc(object sender, RoutedEventArgs e)
{
inputValues input = new inputValues();
calcClass calculations = new calcClass();
try
{
input.pota = Convert.ToDouble(aVar.Text);
input.potb = Convert.ToDouble(bVar.Text);
input.potc = Convert.ToDouble(cVar.Text);
input.potd = Convert.ToDouble(dVar.Text);
input.potf = Convert.ToDouble(fVar.Text);
input.potA = Convert.ToDouble(AVar.Text);
input.potB = Convert.ToDouble(BVar.Text);
input.initStart = Convert.ToDouble(initStart.Text);
input.initEnd = Convert.ToDouble(initEnd.Text);
input.inita = Convert.ToDouble(inita.Text);
input.initb = Convert.ToDouble(initb.Text);
input.initc = Convert.ToDouble(initb.Text);
}
catch
{
MessageBox.Show("Some input values are not of the expected Type.", "Wrong Input", MessageBoxButton.OK, MessageBoxImage.Error);
}
Thread calcthread = new Thread(new ParameterizedThreadStart(calculations.testMethod);
calcthread.Start(input);
}
public class inputValues
{
public double pota, potb, potc, potd, potf, potA, potB;
public double initStart, initEnd, inita, initb, initc;
}
public class calcClass
{
public void testmethod(inputValues input)
{
Thread.CurrentThread.Priority = ThreadPriority.Lowest;
int i;
//the input object will be used somehow, but that doesn't matter for my problem
for (i = 0; i < 1000; i++)
{
Thread.Sleep(10);
}
}
}
如果有人能简单地解释一下如何从testmethod内部更新UI,我将非常感激。由于我是C#和面向对象编程的新手,太复杂的答案我很可能不理解,但我会尽我所能。
此外,如果有人有一个更好的想法一般(也许使用backgroundworker或其他任何东西),我是开放的看到它。
8条答案
按热度按时间6tqwzwtp1#
首先,您需要使用
Dispatcher.Invoke
从另一个线程更改UI,要从另一个类更改UI,您可以使用事件。然后,您可以在主类中注册到该事件,并将更改分派到UI,在计算类中,您可以在希望通知UI时引发该事件:
更新日期:
这似乎仍然是一个经常访问的问题和答案,我想更新这个答案,说明我现在会如何做(使用.NET 4.5)-这会稍微长一点,因为我将展示一些不同的可能性:
@1)如何启动“同步”计算并在后台运行
@2)如何“异步”启动与“等待”:此处,计算在方法返回之前执行并完成,但由于
async
/await
,UI未被阻止(*BTW:此类事件处理程序是async void
的唯一有效用法,因为事件处理程序必须返回void
-在所有其他情况下使用async Task
*)@3)我们现在使用
Task
来代替新的Thread
。为了以后能够检查它的(成功)完成,我们将它保存在全局calcTask
成员中。在后台,这也会启动一个新线程并在那里运行操作,但它更容易处理,并且有一些其他的好处。@4)这里我们也开始了这个动作,但是这次我们返回了任务,所以“异步事件处理程序”可以“等待它”。我们也可以创建
async Task CalcAsync()
,然后创建await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);
(FYI:ConfigureAwait(false)
是为了避免死锁,如果您使用async
/await
,您应该仔细阅读这方面的内容,因为在这里解释太多了),这将导致相同的工作流,但是由于Task.Run
是唯一的“可等待操作”,并且是最后一个操作,我们可以简单地返回任务并保存一次上下文切换,这节省了一些执行时间。@5)这里我现在使用一个“强类型泛型事件”,这样我们就可以轻松地传递和接收“状态对象
@6)这里我使用下面定义的扩展名,它(除了易用性之外)解决旧示例中可能出现的争用情况。在那里,如果事件处理程序在另一个线程中被删除,则可能会在
if
检查之后、调用之前发生事件获取null
的情况。在这里不会发生这种情况,因为扩展获得了事件委托的“副本,”并且在相同的情况下,处理程序仍然在Raise
方法内部注册。sdnqo3pr2#
我要在这里抛给你一个曲线球。如果我说过一次,我已经说过一百次了。像
Invoke
或BeginInvoke
这样的封送处理操作并不总是用辅助线程进度更新UI的最佳方法。在这种情况下,让工作线程将其进度信息发布到一个共享数据结构中,然后UI线程定期轮询该数据结构,通常效果更好。
Invoke
强加的UI和工作线程之间的紧密耦合。BeginInvoke
,则不会出现UI消息队列溢出的风险。Invoke
那样等待UI线程的响应。Invoke
和BeginInvoke
是开销很大的操作。因此,在
calcClass
中创建一个保存进度信息的数据结构。然后在
MainWindow
类中使用DispatcherTimer
来定期轮询进度信息。配置DispatcherTimer
以在最适合您的情况的时间间隔引发Tick
事件。lrl1mhuk3#
您认为应该使用
Dispatcher
更新UI线程上的控件是正确的,长时间运行的进程不应该在UI线程上运行也是正确的。即使您在UI线程上异步运行长时间运行的进程,它仍然会导致性能问题。需要注意的是,
Dispatcher.CurrentDispatcher
将返回当前线程的调度程序,而不一定是UI线程。我认为您可以使用Application.Current.Dispatcher
来获取对UI线程调度程序的引用(如果可用),但如果不可用,则必须将UI调度程序传递到后台线程。通常我使用Task Parallel Library来进行线程操作,而不是
BackgroundWorker
。我只是觉得它更容易使用。例如,
其中
polkgigr4#
与UI交互的所有内容都必须在UI线程中调用(除非它是一个冻结对象)。为此,可以使用调度程序。
我在这里使用了BeginInvoke,通常后台工作者不需要等待UI更新。如果你想等待,你可以使用
Invoke
。但是你应该注意不要频繁地调用BeginInvoke,这会让你非常讨厌。顺便说一下,BackgroundWorker类可以帮助完成这类任务。它允许报告更改,比如百分比,并自动将其从后台线程调度到ui线程。对于大多数线程〈〉更新ui任务,BackgroundWorker是一个很好的工具。
m3eecexj5#
如果这是一个很长的计算,那么我会去后台工作。它有进度支持。它也有取消支持。
这里我有一个绑定到内容的TextBox。
xxb16uws6#
你必须回到你的主线程(也叫
UI thread
)来更新你的UI。任何其他试图更新你的UI的线程都会导致exceptions
被抛出。因此,因为您在WPF中,所以可以使用
Dispatcher
,更具体地说,可以在dispatcher
上使用beginInvoke
。这将允许您在UI线程中执行需要完成的操作(通常是更新UI)。您可能还希望通过维护对控件/窗体的引用,在您的
business
中“注册”UI
,以便可以使用它的dispatcher
。ee7vknir7#
感谢上帝,微软在WPF中解决了这个问题:)
每个
Control
,比如进度条、按钮、窗体等等,上面都有一个Dispatcher
。你可以给予Dispatcher
一个需要执行的Action
,它会在正确的线程上自动调用它(Action
就像一个函数委托)。您可以找到here示例。
当然,您必须让控件可以从其他类访问,例如,将其设置为
public
,并将对Window
的引用传递给其他类,或者只将引用传递给进度条。o75abkj48#
我觉得有必要添加这个更好的答案,因为除了
BackgroundWorker
之外,似乎没有什么能帮到我,而且到目前为止,处理这个问题的答案非常不完整。下面是更新名为MainWindow
的XAML页面的方法,该页面具有如下所示的Image标记:使用
BackgroundWorker
进程显示您是否已连接到网络:如欲了解更多信息,请访问:https://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx