【问题标题】:MultiThreading c# WPF多线程 c# WPF
【发布时间】:2018-04-23 04:08:03
【问题描述】:

我在将值从 c# 代码绑定到 WPF UI 时遇到问题。 我已经了解了线程的基础知识,并且知道我必须使用 Dispatcher 来绑定自定义后台线程中的 ui 线程。

我有一个需求,我想通过每秒访问 nse-stockmarket api 并相应地执行一些逻辑来不断更新我的 WPF UI,以便我可以显示天气股价正在上涨或下跌。

下面是我试图实现这一目标的代码......

注意:我没有遇到任何异常,甚至“跨线程”也没有

 //globally declared var stockName = "";
 //wpf button click 
 private void Button_Click(object sender, RoutedEventArgs e)
  {

      stockName = "LUPIN";
      new Thread(() =>
          {
            RunStockParallel(share.Key);
            Action action = new Action(SetTextBoxValues);

          }).Start();

   }    



public void RunStockParallel(string stockName){
  var count = 0 ;
           do
            {
                HttpWebRequest stocks = null;
                try
                {
                    //your logic will be here.. 
                }
                catch (Exception e)
                {
                    //throw e;
                }


      //It will call the delegate method so that UI can update. 
                Action action = new Action(SetTextBoxValues);

                stockName = count++;
            } while (true);
}




 private void SetTextBoxValues()
        {
            this.Dispatcher.Invoke(() =>
            {

                this.text1.Text = stockName;

            });

        }

当我使用 do-while 循环时,它会一直循环,直到我终止应用程序。在这个 do-while 循环中,我不断尝试通过使用“counter++;”更新 Text1 文本框来更新 WPF ui。

但它没有按预期工作。需要建议或解决方案。 :)

【问题讨论】:

  • 这是一种非常古老的方法来运行并发任务,考虑使用任务,异步和等待,可能还有 WaitAll
  • 除了@TheGeneral 的评论,您可能还想看看DispatcherTimer
  • 你正在创建一个动作,但没有对它做任何事情。为什么不直接调用方法?但是其他人已经提到了使用 async/await 等更好的方法。
  • @MickyD 我已经解决了这个跨线程错误。即使下面的代码也没有抛出任何异常,它继续运行没有任何异常。唯一的问题是我的 ui 没有得到更新..

标签: c# wpf multithreading stock


【解决方案1】:

您没有调用您正在创建的委托。此外,您要递增的变量不是您用于更新 UI 的变量。您只是升级方法 RunStockParallel() 的局部变量。

以下是工作版本。希望对您有所帮助。

PS:我建议不要在生产中使用下面的代码。当你关闭你的应用程序时,SetTextBoxValues() 会抛出一个TaskCanceledException 这一点都不理想。正如有人已经提到的,这可能是执行并发任务的一种非常老式的方式。您可能希望切换到使用基于Taskasync/await 的方法,您可以通过使用CancellationToken 非常有效地避免此类异常。

private void Button_Click(object sender, RoutedEventArgs e)
  {

     stockName = "LUPIN";
     new Thread(() =>
        {
           RunStockParallel(stockName);
           Action action = new Action(SetTextBoxValues); // Maybe this is not required? But this was present in your original code, so I left it as is.
        }).Start();
  }

  public void RunStockParallel(string stockName)
  {
     var count = 0;
     do
     {
        HttpWebRequest stocks = null;
        try
        {
           //your logic will be here.. 
        }
        catch (Exception e)
        {
           //throw e;
        }

        //It will call the delegate method so that UI can update. 
        Action action = new Action(SetTextBoxValues);
        //Invoke the delegate
        action();
        //Increment the globally declared var stockname
        this.stockName = count++.ToString();

     } while (true);
  }

  private void SetTextBoxValues()
  {
     this.Dispatcher.Invoke(() =>
           {
              this.text1.Text = stockName;
           });
  }

【讨论】:

    【解决方案2】:

    @tushardevsharma

    我得到了这个答案。我在 RunStockParallel 方法中的代码的下面添加了 try catch 块中的代码。我的主要逻辑部分与此

    HttpWebRequest stocks = null;
    try
    {
       //your logic will be here.. 
    
           Dispatcher.BeginInvoke(new Action(() =>
           {
               txtName.Text = stockName;
    
    
           }), DispatcherPriority.Background);
    
    }
    catch (Exception e)
    {
       //throw e;
    }
    

    【讨论】:

      【解决方案3】:

      我会用 WPF 的方式来做,所以你不需要关心使用 Dispatcher...

      XAML 代码:

      <Window x:Class="WpfApplication1.MainWindow"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:local="clr-namespace:WpfApplication1"
              mc:Ignorable="d"
              Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignInstance local:ViewModel}">
          <StackPanel>
              <Button Command="{Binding StartPollingCommand}" Content="Start Polling" />
              <TextBlock Text="{Binding StockValue}" />
          </StackPanel>
      </Window>
      

      您的 C# 代码:

      public partial class MainWindow {
          public MainWindow() {
              InitializeComponent();
              DataContext = new ViewModel();
          }
      }
      
      public class ViewModel : INotifyPropertyChanged {
          private string _stockValue;
          public event PropertyChangedEventHandler PropertyChanged;
      
          public ICommand StartPollingCommand {
              get { return new RelayCommand(param => DoExecuteStartPollingCommand()); }
          }
      
          private void DoExecuteStartPollingCommand() {
              try {
                  Task.Run(() => RunStockParallel("StockName"));
              } catch (Exception ex) {
                  //TODO
              }
          }
      
          private void RunStockParallel(string stockName) {
              var count = 0;
              do {
      
                  try {
                      // Do Something to get your Data
                      //HttpWebRequest stocks = null;
                      var stockresults = DateTime.Now;
                      StockValue = stockresults.ToString();
                  } catch (Exception e) {
                      //throw e;
                  }
                  //Wait some time before getting the next stockresults
                  Thread.Sleep(1000);
              } while (true);
          }
      
          public string StockValue {
              get => _stockValue;
              set {
                  _stockValue = value;
                  OnPropertyChanged();
              }
          }
      
          [NotifyPropertyChangedInvocator]
          protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
              PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
          }
      }
      
      public class RelayCommand : ICommand {
          #region Fields
      
          readonly Action<object> _execute;
          readonly Predicate<object> _canExecute;
      
          #endregion // Fields
      
          #region Constructors
      
          /// <summary>
          /// Creates a new command that can always execute.
          /// </summary>
          /// <param name="execute">The execution logic.</param>
          public RelayCommand(Action<object> execute)
              : this(execute, null) { }
      
          /// <summary>
          /// Creates a new command.
          /// </summary>
          /// <param name="execute">The execution logic.</param>
          /// <param name="canExecute">The execution status logic.</param>
          public RelayCommand(Action<object> execute, Predicate<object> canExecute) {
              if (execute == null)
                  throw new ArgumentNullException("execute"); //NOTTOTRANS
      
              _execute = execute;
              _canExecute = canExecute;
          }
      
          #endregion // Constructors
      
          #region ICommand Members
      
          [DebuggerStepThrough]
          public bool CanExecute(object parameter) {
              return _canExecute == null ? true : _canExecute(parameter);
          }
      
          public event EventHandler CanExecuteChanged {
              add => CommandManager.RequerySuggested += value;
              remove => CommandManager.RequerySuggested -= value;
          }
      
          public void Execute(object parameter) {
              _execute(parameter);
          }
      
          #endregion // ICommand Members
      }
      

      您应该在关闭应用程序之前顺利结束正在运行的Task...

      【讨论】:

        【解决方案4】:

        我不知道您遇到了什么错误,但假设您遇到了跨线程操作无效错误。可能是因为这条线

        this.text1.Text = stockName;
        

        您正在直接访问 text1,因此将向您显示有关跨线程的错误。一种安全的方法是使用委托函数调用 .Text 方法

        this.text1.Invoke(new Action(() => this.text1.Text = stockName));
        

        我没有测试过,但你有这个想法。 如果您想进行跨线程安全调用。你可以参考这个

        Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on

        【讨论】:

        • 上面的代码在 windows 窗体应用程序中工作,但在 WPF 中你必须使用 Dispatcher.. 我已经使用过......
        【解决方案5】:

        您在代码中的问题是没有睡眠。线程太忙了。 您需要在 while 循环中添加 Thread.Sleep(100) 或使用信号量。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-16
          • 2016-06-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多