Проблема с общими ресурсами при многопоточности в WPF C # - PullRequest
0 голосов
/ 17 сентября 2018

Итак, я работаю над простым приложением WPF, которое должно считывать информацию со «выделенной страницы» в нашей локальной сети и выводить ее более эффективно. Поэтому сейчас я пытаюсь автоматически обновлять содержимое меток после того, как выбран IP-адрес для получения данных. План состоит в том, чтобы обновлять страницу каждую минуту или около того, чтобы обновить вывод. Проблема в том, что я пробовал много решений о многопоточности, но все еще получаю ту же ошибку: InvalidOperationException: вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им. в основном я понимаю, что поток mainWindows владеет содержимым меток, поэтому я не могу обновить их после чтения из указанного файла / строки.

Вот мой xaml для окна:

  <Window x:Class="PartCountBello.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:PartCountBello"
          mc:Ignorable="d"
          Title="Controllo Macchine" Height="337.42" Width="366.586" 
  ResizeMode="CanMinimize">
      <Grid Margin="0,10,2,12">
          <Grid.ColumnDefinitions>
              <ColumnDefinition/>
              <ColumnDefinition Width="0*"/>
          </Grid.ColumnDefinitions>
          <ComboBox x:Name="cmbNomiMacchine" HorizontalAlignment="Left" 
  Margin="44,74,0,0" VerticalAlignment="Top" Width="129" Height="22" 
  SelectionChanged="cmbNomiMacchine_SelectionChanged"/>
          <Label x:Name="lblNomeDato" Content="PartCount :" 
  HorizontalAlignment="Left" Margin="44,162,0,0" VerticalAlignment="Top" 
  Height="26" Width="129"/>
          <Label x:Name="lblPartCount" Content="" HorizontalAlignment="Left" 
  VerticalAlignment="Top" Margin="178,162,0,0" Height="26" Width="144" 
  RenderTransformOrigin="0.071,0.731"/>
          <Label x:Name="lblSelectInfos" Content="Selezionare macchina" 
  HorizontalAlignment="Left" Margin="44,43,0,0" VerticalAlignment="Top" 
  Width="129" Height="26"/>
          <Label x:Name="lblLavoro" Content="Mansione : " 
  HorizontalAlignment="Left" Margin="44,210,0,0" VerticalAlignment="Top" 
  Width="129"/>
          <Label x:Name="lblMansione" Content="" HorizontalAlignment="Left" 
  Margin="178,210,0,0" VerticalAlignment="Top" Width="144"/>
          <Button x:Name="btnRefresh" Content="Aggiorna" 
  HorizontalAlignment="Left" Margin="247,74,0,0" VerticalAlignment="Top" 
  Width="75" Height="22" Click="btnRefresh_Click"/>
          <Label x:Name="lblMoreInfos" Content="" HorizontalAlignment="Left" 
  Margin="10,241,0,0" VerticalAlignment="Top" Width="339" Height="35"/>

      </Grid>
  </Window>

и вот мой основной код Windows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace PartCountBello
{
    /// <summary>
    /// Logica di interazione per MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        List<string> datas = new List<string> { };      //list that contains machines names and relatives ips. each ip is in the previous index...
                                                        //respect to the machine. 
        public MainWindow()
        {
            InitializeComponent();
            try
            {
                ReadFile rf = new ReadFile();                       //creates the file reader we 're using to...
                datas = rf.getListFromFile("MyIPsFile");   //get all our datas from our file.. Not posting the real file name here cuz you never know. still contains only the ips to connect to.
                for(int i = 2;i<=datas.Count-1;i+=3)
                {
                    cmbNomiMacchine.Items.Add(datas[i]);
                }
            }
            catch (Exception ex)
            {
                lblMoreInfos.Content = ex.Message;
            }
            finally
            { 
            }
        }

        /// <summary>
        /// when the selection is changed to an item it will update the labels contents through a method in the class activityChecker.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            updateLabelsContent();
        }

        /// <summary>
        /// gets the index the item selected in the combobox has in the file to pass it to the fileread and get the ip.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private int getSelectedItemIndex(int index)
        {
            int FIRST_REAL_INDEX = 2;
            int realIndex;
            if (index == 0)
                return 0;
            else
                return realIndex=FIRST_REAL_INDEX+index*2;
        }

        /// <summary>
        /// checks if the machine we are trying to connect to is on, if so updates the dedicated lables' content, else prints a simple message down below.
        /// </summary>
        private void updateLabelsContent()
        {
            string toShow;//the auxiliary string we are going to use to output on labels.
            ActivityChecker ac = new ActivityChecker();
            lblMoreInfos.Content = "";
            if (getSelectedItemIndex(cmbNomiMacchine.SelectedIndex) != 0)
            {
                if (PingHost(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex) - 1]))
                {
                    toShow = ac.getPartCount(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex)]);
                    lblPartCount.Content = toShow;
                    toShow = ac.getJob(datas[getSelectedItemIndex(cmbNomiMacchine.SelectedIndex)]);
                    lblMansione.Content = toShow;
                }
                else
                {
                    lblMoreInfos.Content = "La macchina è al momento spenta.";
                }
            }
            else
            {
                lblMansione.Content = "";
                lblPartCount.Content = "";
            }
        }

        /// <summary>
        /// updates the content of the machine dedicated labels.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRefresh_Click(object sender, RoutedEventArgs e)
        {
                updateLabelsContent(); 
        }

        /// <summary>
        /// allows to verify if the machine we are trying to connect to is on before we actually try to, avoids some freezes.
        /// </summary>
        /// <param name="nameOrAddress"></param>
        /// <returns></returns>
        public static bool PingHost(string nameOrAddress)
        {
            bool pingable = false;
            Ping pinger = new Ping();
            try
            {
                PingReply reply = pinger.Send(nameOrAddress);
                pingable = reply.Status == IPStatus.Success;
            }
            catch (PingException)
            {
                // Discard PingExceptions and return false;
            }
            return pingable;
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 18 сентября 2018

Я не поддерживаю этот WinForms-подобный подход, возможно, есть более подходящие решения, использующие привязки, но это потребует большого количества изменений.Если вам нужно сделать это так и сейчас:

public partial class MainWindow : Window
{
    ....

    private readonly SynchronizationContext uiContext;

    public MainWindow()
    {
        InitializeComponent();

        //controls created on a specific thread can only be modified by that thread.
        //(99.99999%) of the time controls are created and modified on the UI thread
        //SynchronizationContext provides a way to capture and delegate work to the UI thread (it provides more functionality than this, but in your case this is what interests you)
        //We capture the UI Synchronization Context so that we can queue items for execution to the UI thread. We know this is the UI thread because we are in the constructor for our main window
        uiContext = SynchronizationContext.Current;
        ....
    }

    private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        UpdateOnUIThread();
    }

    ...............

    ///I Chose to write another method for clarity, feel free to rework the code anyway you like. Ideally you want to only delegate short work to the UI thread (say textbox.Text = "". This is just here to show the concept
   private void UpdateOnUIThread()
   {
       //Post is asynchronous so will give controll back immediately. If you want synchronous operation, use Send
       uiContext.Post(new SendOrPostCallback((o) => { updateLabelsContent(); }), null);
   }

   ..............
}

}

0 голосов
/ 18 сентября 2018

Хорошо, я нашел решение.

добавил это в mainwindows.cs:

        /// <summary>
        /// private method that manages the thread for the automatic update.
        /// </summary>
        private void updateLabelsContentThread()
        {
            while(true)
            {
                Thread.Sleep(TimeSpan.FromSeconds(10));
                Dispatcher.Invoke(new Action(() => { updateLabelsContent(); }));
            }
        }

и изменил метод события для выбора машины на этот.

/// <summary>
        /// when the selection is changed to an item it will update the labels contents through a method in the class activityChecker.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbNomiMacchine_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            updateLabelsContent();
            Thread t = new Thread(updateLabelsContentThread);
            t.Start();

        }

отлично работает для меня, оставляя это здесь на случай, если кому-то это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...