2012-03-07 29 views
32

我目前正在C#上编写我的第一个程序,而且我对该语言非常陌生(以前只用于C语言)。我做了很多研究,但所有的答案都过于笼统,我根本无法解决问题。如何更新另一个线程中运行的另一个线程的用户界面

所以在这里我的(很常见)问题: 我有一个WPF应用程序,它接受来自用户填充的几个文本框的输入,然后使用它进行大量的计算。他们应该花2-3分钟左右,所以我想更新一个进度条和一个文本块,告诉我目前的状态是什么。 另外我需要存储来自用户的UI输入并将它们提供给线程,所以我有第三个类,我使用它来创建一个对象并希望将此对象传递给后台线程。 显然我会在另一个线程中运行计算,所以UI不会冻结,但我不知道如何更新UI,因为所有计算方法都是另一个类的一部分。 经过大量的研究,我认为最好的方法是使用调度员和TPL,而不是背景工作者,但老实说,我不知道他们是如何工作的,经过大约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或其他),我很乐意看到它。

+2

这是一个古老而又回答的问题,但我非常受欢迎,尽管我会分享这个给那些希望在线程之间实现一个非常简单的“进度记者”的人。使用课程进度。 Stephan Cleary在这篇文章中详细介绍了实现:http://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html。 – SeanOB 2016-11-10 04:55:25

回答

52

首先,您需要使用Dispatcher.Invoke从另一个线程更改UI并从另一个类执行该操作,则可以使用事件。
然后,你可以注册到主类事件(S)和调度更改UI,并在计算类时要通知UI你扔事件:

class MainWindow 
{ 
    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 
{ 
    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)运行它们如何启动它“异步”和“等待它”:这里的计算是在方法返回之前执行和完成的,但是由于这个UI不会被阻塞()。顺便说一下,这样的事件处理程序是async void的唯一有效用法,事件处理程序必须返回void - 在所有其他情况下使用async Task

@ 3)我们现在使用Task而不是新的Thread。为了以后能够检查它的(成功)完成,我们将它保存在全球calcTask成员中。在背景中,这也启动了一个新的线程并在那里执行动作,但它更容易处理,并具有其他一些优点。

@ 4)在这里我们也开始动作,但这次我们返回任务,所以“异步事件处理程序”可以“等待它”。我们还可以创建async Task CalcAsync()然后await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);(仅供参考:ConfigureAwait(false)是为了避免死锁,如果您使用/await,您应该仔细阅读此内容,因为这将在这里解释很多),这会导致相同的工作流程,但作为Task.Run是唯一的“等待操作”,是最后一个我们可以简单地返回任务并保存一个上下文切换,这节省了一些执行时间。

@ 5)在这里,我现在用一个“强类型的一般事件”,所以我们可以传递和接收我们的“状态对象”易

@ 6)这里我用从易于下面定义的扩展,它(除在旧例子中解决可能的竞争条件。在那里可能发生事件nullif -check之后,但在调用之前,如果事件处理程序在此时的另一个线程中被删除。这不会发生在这里,因为扩展名获得事件委托的“副本”,并且在相同的情况下,处理程序仍然在Raise方法中注册。

+0

感谢您的快速回答, 我将此代码添加到我的代码中,但是在CalcClass中,我遇到“ProgressUpdate”问题,该类无法识别它。 有什么我必须包括使用它?到目前为止,我只是添加了“使用System.Threading”。 – phil13131 2012-03-07 14:16:56

+0

是的,我收到以下错误消息:错误无效标记';'在类,结构体或接口成员声明中使用 – phil13131 2012-03-07 14:25:59

+0

我使用上述代码,并在“公共事件ProgressUpdate;”行上获取错误被宣布。 – phil13131 2012-03-07 14:37:21

0

感谢上帝,微软得到了在WPF :)

Control它想通了,就像一个进度条,按钮,形式等方面有着Dispatcher。您可以给DispatcherAction需要执行,它会自动在正确的线程上调用它(Action就像一个函数委托)。

你可以找到一个例子here

当然,您必须让控件可以从其他类访问,例如,通过使其成为public并将对Window的引用交给其他课程,或者仅将引用传递给进度栏。

+3

请注意,当你链接到一个例子,而不是在这里复制/粘贴相关的代码,如果该网页有史以来(如今天早上)或删除其内容,这个答案然后没有它需要的人的内容去工作。最好包含该内容。 – vapcguy 2016-08-18 14:30:03

1

您将不得不回到您的主线程(也称为UI thread)以便update的UI。 任何其他线程试图更新你的用户界面只会导致exceptions被抛到处都是。

因此,因为您处于WPF中,因此您可以在此dispatcher上使用Dispatcher,更具体地说,使用beginInvoke。这将允许您在UI线程中执行需要完成的操作(通常更新UI)。

您还希望通过维护对控件/窗体的引用在您的business中“注册”UI,因此您可以使用它的dispatcher

+0

WPF新手的语法示例会很有帮助。 – vapcguy 2016-08-18 14:30:37

4

所有与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*/)); 

我在这里使用BeginInvoke,通常一个backgroundworker不需要等待UI更新。如果你想等待,你可以使用Invoke。但是你应该小心不要经常调用BeginInvoke来加快速度,这会变得非常糟糕。

顺便说一句,BackgroundWorker类有助于这种taks。它允许报告更改,如百分比,并自动从后台线程分派到ui线程中。对于最线程的<>更新ui任务,BackgroundWorker是一个很棒的工具。

1

如果这是一个很长的计算,那么我会去后台工作。它有进步支持。它也支持取消。

http://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx 

这里我有一个文本框绑定到内容。

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"); 
     } 
    } 
+0

我会对此进行投票,因为它让我走上了正确的轨道,并且拥有了我需要的信息的MSDN页面,但这是一个完整示例中的FAR。你需要显示更多的信息,比如在哪里实例化worker,属性和事件处理程序给它,显示DoWork函数在做什么以及如何报告它的进度。这只是说明当你取消工作人员并显示'e.Result'时,你可以做些什么,假设你有'backgroundWorker_DoWork(对象发送者,DoWorkEventArgs e){...}'函数给你,而不是'DoWork'实际上是什么做和如何返回作为'结果'。 – vapcguy 2016-08-18 16:06:02

5

你说的没错,你应该使用Dispatcher到UI线程上更新的控制,并且也是正确的长期运行的进程不应该在UI线程上运行。即使您在UI线程上异步运行长时间运行的进程,仍然可能导致性能问题。

应该注意的是,Dispatcher.CurrentDispatcher将返回当前线程的调度程序,不一定是UI线程。我认为你可以使用Application.Current.Dispatcher获得对UI线程调度程序的引用(如果可用),但是如果没有,你必须将UI调度程序传递到后台线程。

通常我使用Task Parallel Library进行线程操作而不是BackgroundWorker。我只是觉得它更易于使用。

例如,

Task.Factory.StartNew(() => 
    SomeObject.RunLongProcess(someDataObject)); 

其中

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))); 
     } 
    } 
} 
28

,我要在这里你扔一个曲线球。如果我说过一次,我已经说过一百次了。诸如InvokeBeginInvoke之类的封送操作并不总是用工作线程进度更新UI的最佳方法。

在这种情况下,让工作线程将其进度信息发布到UI线程定期轮询的共享数据结构通常效果更好。这有几个优点。

  • 它打破了Invoke强加的UI和工作线程之间的紧密耦合。
  • UI线程可以在UI控件得到更新时得到指示......当你真正想到它时它应该是这样。
  • 如果在工作线程中使用BeginInvoke,就不会有超出UI消息队列的风险。
  • 工作线程不必等待来自UI线程的响应,就像Invoke那样。
  • 您可以在UI和工作线程上获得更高的吞吐量。
  • InvokeBeginInvoke是昂贵的操作。

所以在你的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); 
    } 
    } 
} 

然后在您的MainWindow类使用DispatcherTimer定期轮询进度的信息。配置DispatcherTimer以在任何适合您情况的时间间隔内提高Tick事件。

public partial class MainWindow : Window 
{ 
    public void YourDispatcherTimer_Tick(object sender, EventArgs args) 
    { 
    YourProgressBar.Value = calculation.PercentComplete; 
    } 
} 
+1

你好布赖恩,我认为你的答案简单而优雅。在你看来,我想知道dispatchertimer tick挨烧的速度有多快?根据您的经验,什么是可接受的更新率?谢谢。 – DoubleDunk 2014-02-03 21:34:43

+2

@DoubleDunk:或许在500ms到2000ms之间。任何更快,用户将无法区分。任何慢,用户会想知道为什么进度栏不增加。确定最适合您的应用程序和用户的良好平衡。 – 2014-02-05 15:51:15

+0

@BrianGideon更新率应该取决于计算大概需要多长时间。如果它每秒更新两次,整个计算只需要约3秒,那么进度条仍然会有较大的跳跃。另一方面,如果计算结果花费20分钟(就像我们在一个案例中那样),那么500毫秒就太频繁了。 不错的答案,虽然和调用一个很好的选择! – phil13131 2015-02-04 16:52:59

0

感到有必要增加这更好的答案,如无除了BackgroundWorker似乎帮助我,和处理,答案迄今可悲的是不完整的。这是你将如何更新名为MainWindow一个XAML页面中图像标签是这样的:

<Image Name="imgNtwkInd" Source="Images/network_on.jpg" Width="50" /> 

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; 
    } 
} 

对于更多信息:https://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx

相关问题