我目前正在用 C# 编写我的第一个程序,我对这门语言非常陌生(以前只使用 C#)。我做了很多研究,但所有的答案都太笼统了,我根本无法让它发挥作用。
所以这是我的(非常常见的)问题: 我有一个 WPF 应用程序,它从用户填写的几个文本框中获取输入,然后使用它来对它们进行大量计算。它们大约需要 2-3 分钟,所以我想更新一个进度条和一个文本 block ,告诉我当前状态是什么。 我还需要存储来自用户的 UI 输入并将它们提供给线程,所以我有第三个类,我用它来创建一个对象并想将该对象传递给后台线程。 显然我会在另一个线程中运行计算,所以 UI 不会卡住,但我不知道如何更新 UI,因为所有计算方法都是另一个类的一部分。 经过大量研究,我认为最好的方法是使用调度员和 TPL,而不是后台 worker ,但老实说,我不确定它们是如何工作的,经过大约 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);
}
}
}
如果有人能简单解释一下如何从测试方法内部更新 UI,我将不胜感激。由于我是 C# 和面向对象编程的新手,太复杂的答案我很可能无法理解,但我会尽力而为。
此外,如果有人总体上有更好的想法(可能使用 backgroundworker 或其他任何东西),我愿意看到它。
最佳答案
首先,您需要使用 Dispatcher.Invoke
从另一个线程更改 UI,并从另一个类执行此操作,您可以使用事件。
然后您可以在主类中注册该事件并将更改分派(dispatch)给 UI,并在计算类中在您想要通知 UI 时抛出事件:
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
// alternative version to the extension for C# 6+:
ProgressUpdate?.Invoke(this, new EventArgs<YourStatus>(status));
//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
/await
UI 未被阻塞(顺便说一句:此类事件处理程序是 async void
的唯一有效用法,因为事件处理程序必须返回 void
- 使用 async Task
在所有其他情况下)
@3) 我们现在使用Task
而不是新的Thread
。为了稍后能够检查其(成功)完成,我们将其保存在全局 calcTask
成员中。在后台这也会启动一个新线程并在那里运行操作,但它更容易处理并且还有一些其他好处。
@4) 这里我们也开始了 Action ,但是这次我们返回了任务,所以“异步事件处理程序”可以“等待它”。我们还可以创建 async Task CalcAsync()
,然后创建 await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);
(仅供引用: ConfigureAwait(false)
是为了避免死锁,如果您使用 async
/await
,您应该仔细阅读它,因为它会太多在这里解释)这将导致相同的工作流程,但由于 Task.Run
是唯一的“等待操作”并且是最后一个我们可以简单地返回任务并保存一次上下文切换,从而节省一些执行时间。
@5) 我现在在这里使用“强类型通用事件”,这样我们就可以轻松地传递和接收我们的“状态对象”
@6) 这里我使用下面定义的扩展,它(除了易用性之外)解决了旧示例中可能出现的竞争条件。在 if
-check 之后,事件可能会发生 null
,但在调用之前,如果事件处理程序就在那一刻在另一个线程中被删除。这不会在这里发生,因为扩展获得了事件委托(delegate)的“副本”,并且在相同情况下,处理程序仍然在 Raise
方法中注册。
关于c# - 如何从另一个类中运行的另一个线程更新 UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9602567/