C# 如何从另一个类中运行的另一个线程更新 UI
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9602567/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to update UI from another thread running in another class
提问by philkark
I am currently writing my first program on C# and I am extremely new to the language (used to only work with C so far). I have done a lot of research, but all answers were too general and I simply couldn't get it t work.
我目前正在用 C# 编写我的第一个程序,我对这门语言非常陌生(到目前为止,我只使用 C 语言)。我做了很多研究,但所有的答案都太笼统了,我根本无法解决。
So here my (very common) problem: I have a WPF application which takes inputs from a few textboxes filled by the user and then uses that to do a lot of calculations with them. They should take around 2-3 minutes, so I would like to update a progress bar and a textblock telling me what the current status is. Also I need to store the UI inputs from the user and give them to the thread, so I have a third class, which I use to create an object and would like to pass this object to the background thread. Obviously I would run the calculations in another thread, so the UI doesn't freeze, but I don't know how to update the UI, since all the calculation methods are part of another class. After a lot of reasearch I think the best method to go with would be using dispatchers and TPL and not a backgroundworker, but honestly I am not sure how they work and after around 20 hours of trial and error with other answers, I decided to ask a question myself.
所以这里是我的(非常常见的)问题:我有一个 WPF 应用程序,它从用户填写的几个文本框中获取输入,然后使用它对它们进行大量计算。他们应该需要大约 2-3 分钟,所以我想更新一个进度条和一个文本块,告诉我当前状态是什么。此外,我需要存储来自用户的 UI 输入并将它们提供给线程,所以我有第三个类,我用它来创建一个对象,并希望将此对象传递给后台线程。显然我会在另一个线程中运行计算,所以 UI 不会冻结,但我不知道如何更新 UI,因为所有计算方法都是另一个类的一部分。经过大量研究,我认为最好的方法是使用调度员和 TPL 而不是后台工作人员,
Here a very simple structure of my program:
这是我的程序的一个非常简单的结构:
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);
}
}
}
I would be very grateful if someone had a simple explanation how to update the UI from inside the testmethod. Since I am new to C# and object oriented programming, too complicated answers I will very likely not understand, I'll do my best though.
如果有人对如何从测试方法内部更新 UI 有一个简单的解释,我将不胜感激。由于我是 C# 和面向对象编程的新手,太复杂的答案我很可能无法理解,但我会尽力而为。
Also if someone has a better idea in general (maybe using backgroundworker or anything else) I am open to see it.
此外,如果有人总体上有更好的想法(可能使用后台工作人员或其他任何东西),我很乐意看到它。
采纳答案by Christoph Fink
First you need to use Dispatcher.Invoketo change the UI from another thread and to do that from another class, you can use events.
Then you can register to that event(s) in the main class and Dispatch the changes to the UI and in the calculation class you throw the event when you want to notify the UI:
首先,您需要使用Dispatcher.Invoke从另一个线程更改 UI 并从另一个类执行此操作,您可以使用事件。
然后,您可以在主类中注册该事件,并将更改分发到 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
}
}
UPDATE:
As it seems this is still an often visited question and answer I want to update this answer with how I would do it now (with .NET 4.5) - this is a little longer as I will show some different possibilities:
更新:
因为这似乎仍然是一个经常访问的问题和答案,所以我想用我现在的做法(使用 .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) How to start the "synchronous" calculations and run them in the background
@1) 如何启动“同步”计算并在后台运行它们
@2) How to start it "asynchronous" and "await it": Here the calculation is executed and completed before the method returns, but because of the async/awaitthe UI is not blocked (BTW: such event handlers are the only valid usages of async voidas the event handler must return void- use async Taskin all other cases)
@2)如何启动它“异步”和“等待它”:这里的计算在方法返回之前执行并完成,但由于async/ awaitUI没有被阻塞(顺便说一句:此类事件处理程序是唯一有效的用法async void因为事件处理程序必须返回void-async Task在所有其他情况下使用)
@3) Instead of a new Threadwe now use a Task. To later be able to check its (successfull) completion we save it in the global calcTaskmember. In the background this also starts a new thread and runs the action there, but it is much easier to handle and has some other benefits.
@3)Thread我们现在使用Task. 为了稍后能够检查其(成功)完成情况,我们将其保存在全局calcTask成员中。在后台,这也会启动一个新线程并在那里运行操作,但它更容易处理并具有其他一些好处。
@4) Here we also start the action, but this time we return the task, so the "async event handler" can "await it". We could also create async Task CalcAsync()and then await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);(FYI: the ConfigureAwait(false)is to avoid deadlocks, you should read up on this if you use async/awaitas it would be to much to explain here) which would result in the same workflow, but as the Task.Runis the only "awaitable operation" and is the last one we can simply return the task and save one context switch, which saves some execution time.
@4) 这里我们也开始了动作,但是这次我们返回了任务,所以“异步事件处理程序”可以“等待它”。我们也可以创建async Task CalcAsync()然后await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);(仅供参考:这ConfigureAwait(false)是为了避免死锁,如果您使用async/则应该阅读此await内容,因为在这里解释太多)这将导致相同的工作流程,但因为这Task.Run是唯一的“等待操作”,并且是最后一个我们可以简单地返回任务并保存一个上下文切换,从而节省一些执行时间。
@5) Here I now use a "strongly typed generic event" so we can pass and receive our "status object" easily
@5)这里我现在使用“强类型通用事件”,因此我们可以轻松传递和接收我们的“状态对象”
@6) Here I use the extension defined below, which (aside from ease of use) solve the possible race condition in the old example. There it could have happened that the event got nullafter the if-check, but before the call if the event handler was removed in another thread at just that moment. This can't happen here, as the extensions gets a "copy" of the event delegate and in the same situation the handler is still registered inside the Raisemethod.
@6)这里我使用下面定义的扩展,它(除了易用性)解决了旧示例中可能的竞争条件。在那里,如果事件处理程序在另一个线程中被删除,那么事件可能发生null在if-check 之后,但在调用之前。这不能在这里发生,因为扩展获得了事件委托的“副本”,并且在相同的情况下,处理程序仍然在Raise方法中注册。
回答by Vladislav Zorov
Thank God, Microsoft got thatfigured out in WPF :)
感谢上帝,微软在 WPF 中解决了这个问题:)
Every Control, like a progress bar, button, form, etc. has a Dispatcheron it. You can give the Dispatcheran Actionthat needs to be performed, and it will automatically call it on the correct thread (an Actionis like a function delegate).
每个Control,如进度条、按钮、表单等,都有一个Dispatcher。您可以给出需要执行的Dispatcheran Action,它会自动在正确的线程上调用它(anAction就像一个函数委托)。
You can find an example here.
您可以在此处找到示例。
Of course, you'll have to have the control accessible from other classes, e.g. by making it publicand handing a reference to the Windowto your other class, or maybe by passing a reference only to the progress bar.
当然,您必须让其他类可以访问该控件,例如,通过制作它public并将对 的引用传递Window给您的其他类,或者可能只将引用传递给进度条。
回答by squelos
You are going to have to come back to your main thread (also called UI thread) in order to updatethe UI.
Any other thread trying to update your UI will just cause exceptionsto be thrown all over the place.
您将不得不返回主线程(也称为UI thread)才能访问updateUI。任何其他试图更新您的 UI 的线程只会导致exceptions被扔到所有地方。
So because you are in WPF, you can use the Dispatcherand more specifically a beginInvokeon this dispatcher. This will allow you to execute what needs done (typically Update the UI) in the UI thread.
所以,因为你在WPF中,你可以使用Dispatcher和更特别beginInvoke在此dispatcher。这将允许您在 UI 线程中执行需要完成的操作(通常是更新 UI)。
You migh also want to "register" the UIin your business, by maintaining a reference to a control/form, so you can use its dispatcher.
您migh也想“注册”的UI你business,通过保持到控制/表单的引用,所以你可以使用它dispatcher。
回答by dowhilefor
Everything that interacts with the UI must be called in the UI thread (unless it is a frozen object). To do that, you can use the dispatcher.
所有与 UI 交互的东西都必须在 UI 线程中调用(除非它是一个冻结对象)。为此,您可以使用调度程序。
var disp = /* Get the UI dispatcher, each WPF object has a dispatcher which you can query*/
disp.BeginInvoke(DispatcherPriority.Normal,
(Action)(() => /*Do your UI Stuff here*/));
I use BeginInvoke here, usually a backgroundworker doesn't need to wait that the UI updates. If you want to wait, you can use Invoke. But you should be careful not to call BeginInvoke to fast to often, this can get really nasty.
我在这里使用 BeginInvoke,通常后台工作人员不需要等待 UI 更新。如果您想等待,可以使用Invoke. 但是你应该小心不要经常调用 BeginInvoke 来快速调用,这会变得非常糟糕。
By the way, The BackgroundWorker class helps with this kind of taks. It allows Reporting changes, like a percentage and dispatches this automatically from the Background thread into the ui thread. For the most thread <> update ui tasks the BackgroundWorker is a great tool.
顺便说一下,BackgroundWorker 类可以帮助完成这种任务。它允许报告更改,例如百分比,并将其从后台线程自动分派到 ui 线程。对于大多数线程 <> 更新 ui 任务,BackgroundWorker 是一个很好的工具。
回答by paparazzo
If this is a long calculation then I would go background worker. It has progress support. It also has support for cancel.
如果这是一个很长的计算,那么我会去后台工作。它有进度支持。它还支持取消。
http://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx
Here I have a TextBox bound to contents.
在这里,我有一个绑定到内容的 TextBox。
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.Write("backgroundWorker_RunWorkerCompleted");
if (e.Cancelled)
{
contents = "Cancelled get contents.";
NotifyPropertyChanged("Contents");
}
else if (e.Error != null)
{
contents = "An Error Occured in get contents";
NotifyPropertyChanged("Contents");
}
else
{
contents = (string)e.Result;
if (contentTabSelectd) NotifyPropertyChanged("Contents");
}
}
回答by Rachel
You're right that you should use the Dispatcherto update controls on the UI thread, and also right that long-running processes should not run on the UI thread. Even if you run the long-running process asynchronously on the UI thread, it can still cause performance issues.
您应该使用Dispatcher来更新 UI 线程上的控件是正确的,并且长时间运行的进程不应该在 UI 线程上运行也是正确的。即使您在 UI 线程上异步运行长时间运行的进程,它仍然会导致性能问题。
It should be noted that Dispatcher.CurrentDispatcherwill return the dispatcher for the current thread, not necessarily the UI thread. I think you can use Application.Current.Dispatcherto get a reference to the UI thread's dispatcher if that's available to you, but if not you'll have to pass the UI dispatcher in to your background thread.
需要注意的是,Dispatcher.CurrentDispatcher将返回当前线程的调度程序,不一定是 UI 线程。我认为您可以使用它Application.Current.Dispatcher来获取对 UI 线程调度程序的引用,如果它对您可用,但如果没有,则必须将 UI 调度程序传递给您的后台线程。
Typically I use the Task Parallel Libraryfor threading operations instead of a BackgroundWorker. I just find it easier to use.
通常我使用任务并行库进行线程操作而不是BackgroundWorker. 我只是觉得它更容易使用。
For example,
例如,
Task.Factory.StartNew(() =>
SomeObject.RunLongProcess(someDataObject));
where
在哪里
void RunLongProcess(SomeViewModel someDataObject)
{
for (int i = 0; i <= 1000; i++)
{
Thread.Sleep(10);
// Update every 10 executions
if (i % 10 == 0)
{
// Send message to UI thread
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action)(() => someDataObject.ProgressValue = (i / 1000)));
}
}
}
回答by Brian Gideon
I am going to throw you a curve ball here. If I have said it once I have said it a hundred times. Marshaling operations like Invokeor BeginInvokeare not always the best methods for updating the UI with worker thread progress.
我要在这里扔给你一个曲线球。如果我说过一次,我已经说过一百遍了。编组操作(例如Invoke或BeginInvoke并不总是使用工作线程进度更新 UI 的最佳方法)。
In this case it usually works better to have the worker thread publish its progress information to a shared data structure that the UI thread then polls at regular intervals. This has several advantages.
在这种情况下,最好让工作线程将其进度信息发布到共享数据结构,然后 UI 线程定期轮询该数据结构。这有几个优点。
- It breaks the tight coupling between the UI and worker thread that
Invokeimposes. - The UI thread gets to dictate when the UI controls get updated...the way it should be anyway when you really think about it.
- There is no risk of overrunning the UI message queue as would be the case if
BeginInvokewere used from the worker thread. - The worker thread does not have to wait for a response from the UI thread as would be the case with
Invoke. - You get more throughput on both the UI and worker threads.
InvokeandBeginInvokeare expensive operations.
- 它打破了 UI 和工作线程之间
Invoke强加的紧密耦合。 - UI 线程可以决定 UI 控件何时更新......当你真正考虑它时它应该是这样的。
- 如果
BeginInvoke从工作线程使用,则不会出现 UI 消息队列溢出的风险。 - 工作线程不必像
Invoke. - 您可以在 UI 和工作线程上获得更多吞吐量。
Invoke并且BeginInvoke是昂贵的操作。
So in your calcClasscreate a data structure that will hold the progress information.
因此,在您calcClass创建一个将保存进度信息的数据结构中。
public class calcClass
{
private double percentComplete = 0;
public double PercentComplete
{
get
{
// Do a thread-safe read here.
return Interlocked.CompareExchange(ref percentComplete, 0, 0);
}
}
public testMethod(object input)
{
int count = 1000;
for (int i = 0; i < count; i++)
{
Thread.Sleep(10);
double newvalue = ((double)i + 1) / (double)count;
Interlocked.Exchange(ref percentComplete, newvalue);
}
}
}
Then in your MainWindowclass use a DispatcherTimerto periodically poll the progress information. Configure the DispatcherTimerto raise the Tickevent on whatever interval is most appropriate for your situation.
然后在您的MainWindow班级中使用 aDispatcherTimer定期轮询进度信息。配置DispatcherTimer以Tick在最适合您的情况的任何时间间隔引发事件。
public partial class MainWindow : Window
{
public void YourDispatcherTimer_Tick(object sender, EventArgs args)
{
YourProgressBar.Value = calculation.PercentComplete;
}
}
回答by vapcguy
Felt the need to add this better answer, as nothing except BackgroundWorkerseemed to help me, and the answer dealing with that thus far was woefully incomplete. This is how you would update a XAML page called MainWindowthat has an Image tag like this:
觉得有必要添加这个更好的答案,因为除了BackgroundWorker似乎对我没有任何帮助,而且到目前为止处理这个问题的答案非常不完整。这是更新MainWindow具有 Image 标记的 XAML 页面的方式,如下所示:
<Image Name="imgNtwkInd" Source="Images/network_on.jpg" Width="50" />
with a BackgroundWorkerprocess to show if you are connected to the network or not:
有一个BackgroundWorker过程来显示您是否已连接到网络:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
public partial class MainWindow : Window
{
private BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
// Set up background worker to allow progress reporting and cancellation
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
// This is your main work process that records progress
bw.DoWork += new DoWorkEventHandler(SomeClass.DoWork);
// This will update your page based on that progress
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
// This starts your background worker and "DoWork()"
bw.RunWorkerAsync();
// When this page closes, this will run and cancel your background worker
this.Closing += new CancelEventHandler(Page_Unload);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BitmapImage bImg = new BitmapImage();
bool connected = false;
string response = e.ProgressPercentage.ToString(); // will either be 1 or 0 for true/false -- this is the result recorded in DoWork()
if (response == "1")
connected = true;
// Do something with the result we got
if (!connected)
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_off.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
else
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_on.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
}
private void Page_Unload(object sender, CancelEventArgs e)
{
bw.CancelAsync(); // stops the background worker when unloading the page
}
}
public class SomeClass
{
public static bool connected = false;
public void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int i = 0;
do
{
connected = CheckConn(); // do some task and get the result
if (bw.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
Thread.Sleep(1000);
// Record your result here
if (connected)
bw.ReportProgress(1);
else
bw.ReportProgress(0);
}
}
while (i == 0);
}
private static bool CheckConn()
{
bool conn = false;
Ping png = new Ping();
string host = "SomeComputerNameHere";
try
{
PingReply pngReply = png.Send(host);
if (pngReply.Status == IPStatus.Success)
conn = true;
}
catch (PingException ex)
{
// write exception to log
}
return conn;
}
}
For more information: https://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx
更多信息:https: //msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx

![如何将图像从 C# 中的字节 [] 放入图片框中](/res/img/loading.gif)