我目前正在用C#编写我的第一个程序,并且对这种语言非常陌生(到目前为止,仅用于C语言)。我做了很多研究,但是所有答案都太笼统了,我根本无法解决。
所以这是我的(很常见)问题:我有一个WPF应用程序,该应用程序从用户填写的几个文本框中获取输入,然后使用它们对它们进行大量计算。他们应该花费2-3分钟左右的时间,所以我想更新进度条和文本块,告诉我当前的状态是什么。另外,我还需要存储来自用户的UI输入并将其提供给线程,因此,我有一个第三类,用于创建对象并将该对象传递给后台线程。显然,我会在另一个线程中运行计算,因此UI不会冻结,但是我不知道如何更新UI,因为所有计算方法都是另一个类的一部分。经过大量的研究,我认为最好的方法是使用调度程序和TPL,而不是背景工作人员,
这是我程序的非常简单的结构:
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); } } }
如果有人有一个简单的解释如何从测试方法中更新用户界面,我将不胜感激。由于我是C#和面向对象编程的新手,因此我很可能不会理解过于复杂的答案,但我会尽力而为。
同样,如果某人总体上有一个更好的主意(也许使用backgroundworker或其他工具),我也很乐意看到它。
首先,您需要使用Dispatcher.Invoke来从另一个线程更改UI,并从另一个类进行更改,您可以使用事件。 然后,您可以在主类中注册该事件,然后将更改分发给UI,并在要通知UI的计算类中引发事件:
Dispatcher.Invoke
class MainWindow : Window { private void startCalc() { //your code CalcClass calc = new CalcClass(); calc.ProgressUpdate += (s, e) => { Dispatcher.Invoke((Action)delegate() { /* update UI */ }); }; Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod)); calcthread.Start(input); } } class CalcClass { public event EventHandler ProgressUpdate; public void testMethod(object input) { //part 1 if(ProgressUpdate != null) ProgressUpdate(this, new YourEventArgs(status)); //part 2 } }
更新: 看来这仍然是一个经常访问的问题,我想用现在的方式(使用.NET 4.5)更新此答案-这有点长,因为我将展示一些不同的可能性:
class MainWindow : Window { Task calcTask = null; void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1 async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2 { await CalcAsync(); // #2 } void StartCalc() { var calc = PrepareCalc(); calcTask = Task.Run(() => calc.TestMethod(input)); // #3 } Task CalcAsync() { var calc = PrepareCalc(); return Task.Run(() => calc.TestMethod(input)); // #4 } CalcClass PrepareCalc() { //your code var calc = new CalcClass(); calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate() { // update UI }); return calc; } } class CalcClass { public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5 public TestMethod(InputValues input) { //part 1 ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus //part 2 } } static class EventExtensions { public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent, object sender, T args) { if (theEvent != null) theEvent(sender, new EventArgs<T>(args)); } }
@ 1)如何启动“同步”计算并在后台运行它们
@ 2)如何“异步”和“等待”启动它:在这里,在方法返回之前执行并完成了计算,但是由于async/ awaitUI没有被阻塞( 顺便说一句,此类事件处理程序是的唯一有效用法)async void因为事件处理程序必须返回void- async Task在所有其他情况下使用)
async
await
async void
void
async Task
@ 3)Thread现在使用代替新的Task。为了以后能够检查其(成功)完成情况,我们将其保存在全局calcTask成员中。在后台,这还会启动一个新线程并在该线程中运行操作,但是它更易于处理并具有其他一些好处。
Thread
Task
calcTask
@ 4)在这里我们也开始动作,但是这次我们返回任务,因此“异步事件处理程序”可以“等待它”。我们还可以创建async Task CalcAsync()然后await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);(FYI:ConfigureAwait(false)避免死锁,如果您使用async/ ,则应仔细阅读此内容,await因为这里将对此进行大量解释),这将导致相同的工作流程,但Task.Run唯一的“等待操作”,这是最后一个操作,我们可以简单地返回任务并保存一个上下文切换,从而节省了一些执行时间。
async Task CalcAsync()
await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);
ConfigureAwait(false)
Task.Run
@ 5)现在,我在这里使用“强类型通用事件”,以便我们可以轻松地传递和接收“状态对象”
@ 6)在这里,我使用下面定义的扩展名(除了易于使用之外)解决了旧示例中可能出现的竞争情况。如果此时恰好在另一个线程中删除了事件处理程序,则可能发生了该事件null在if-check之后但在调用之前发生的情况。这不可能在这里发生,因为扩展获取了事件委托的“副本”,并且在相同情况下,处理程序仍在Raise方法内注册。
null
if
Raise