WPF wątki

0

Część!
Potrzebuje przekazać wartość z nowego wątku do canvas , niestety pojawia mi się wyjątek nie mogę uzyskać dostępu do tego obiektu ponieważ należy on do innego wątku. Próbowałem metody Invoke lecz to nie pomaga jakieś rozwiązania ?

0

To generalnie dość częsty problem i wszystko zależy od sytuacji. Można np użyć volatile przy deklarowaniu zmiennej

0

Rzeczy związane z głównym wątkiem nie pisz w komentarzach. Zadeklaruj sobie tą zmienną globalnie ,zmodyfikuj ją w wątku i wtedy w canvasie odczytaj ją po modyfikacji. Lub przekaż ją do nowego wątki poprzez parametr funkcji

     public partial class MainWindow : Window
    {
        volatile string tochange = "";
        public  MainWindow()
        {
            InitializeComponent();
            Example.Content = tochange;
            
        }
        public void changeString()
        {
            tochange = "TeextAfterChange";
        }
        private async void Example_Click(object sender, RoutedEventArgs e)
        {
            await Task.Run(() => changeString());
            Example.Content = tochange;
        }
    }
}

Tutaj mały kod pokazowy nie ma on żadnego sensu. Chodzi tylko o pokazanie jak to mniej więcej można zmienić. Można też czasami zrobić po prostu tak

     public partial class MainWindow : Window
    {
       
        public  MainWindow()
        {
            InitializeComponent();
            Example.Content = "";
            
        }
        public string changeString(string tochange)
        {
            tochange = "TeextAfterChange";
            return tochange;

        }
        private async void Example_Click(object sender, RoutedEventArgs e)
        {
            string tochange = "";
            tochange= await Task.Run(() => changeString(tochange));
            Example.Content = tochange;
        }
    }

Tutaj bez volitate czasami lepiej tak czasami lepiej pierwszym sposobem zależy od sytuacji.

0
private void AsyncOperationCompleted(object sender, object args)
{
     this.Dispatcher?.Invoke(() => {
          myCanvas.DataContext = null;
     }
}
2
Botek napisał(a):

Polecam sobie poczytać o volatile, między innymi wpis na blogu jednego z twórców języka:
https://ericlippert.com/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/

W skrócie - jeśli używasz volatile to robisz coś źle, w zasadzie zawsze są mechanizmy wyżej poziomowe i nigdy nie powinno się go używać.
Jeżeli używasz dwóch wątków do czytania i zapisywania tego samego miejsca w pamięci to powinieneś to robić w sekcjach krytycznych lock. Użycie locka automatycznie tworzy "barierę" pamięci i zapewnia że wartość odczytana po locku będzie aktualna. Do synchronizacji między wątkami, zamiast wartości volatile lepiej użyć np ManualResetEvent(Slim). "volatile" można użyć jedynie jeśli samemu pisze się niskopoziomową klasę do synchronizacji, ale zazwyczaj wystarczają gotowe klasy.
Zakładam że wcześniej @Botek pisałeś w javie i stąd te naszłości, bo w javie locki nie działają w ten sposób i czasem volatile się tam spotyka.

W dodatku volatile nie ratuje tutaj od wymienionego problemu, tutaj chodzi o checka w kontrolkach UI czy wątek który się do nich odwołuje jest tym samym z którego utworzono kontrolkę. Warto wspomnieć że wyjątek jest rzucany tylko w trybie debug a w trybie release ma to szansę działania, choć rezultaty mogą być przypadkowe.

Oczywiście rozwiązaniem jest tutaj kontrolka.Invoke lub SynchronizationContext który opakowuje odpowiednie metody, ale działa tak samo dla WPF, WinForms i ASP.NET.
Pokaż swój kod jeśli Invoke Ci tutaj nie pomógł, bo musi.

@Grzegorz Świdwa wszystko fajnie tylko po co tworzyć jakieś wrappery na świetnie działające metody asynchroniczne? Trzy dodatkowe klasy i kilkadziesiąt linii kodu które nie wprowadzają niczego dobrego. Generalnie próbowałeś stworzyć własny BackgroundWorker który już jest zaimplementowany we frameworku ale jest uznany za przestarzały i został wyparty przez "async/await".

Wystarczy zwykłe "await" i "try/catch" zamiast e.Success.

    public partial class MainWindow : Window
    {
        public MainWindow()
        { 
            InitializeComponent();
            InitAsync();
       }

       public async Task InitAsync()
       {
           try
           {
               var args = "Tekst 1";
               var result = await Task.Run(() => args + " i Tekst 2");
               MyTextBox.Text = result;
          }
          catch (Exception e)
          {
              MessageBox.Show(e.GetType() + " - " + e.Message);
          }
      }
    }

Dużo prościej, mniej kodu, brak dodatkowych klas, prosta obsługa wyjątków i w dodatku nie trzeba używać Invoke bo "await" automatycznie wróci do wątku UI

0

Naprawiłem :)
ta linijka wszystko naprawiła, a tak naprawdę to po przecinku :)

  Dispatcher.BeginInvoke(new Action(DrawLine), DispatcherPriority.ApplicationIdle);

dzięki wszystkim za pomoc

1 użytkowników online, w tym zalogowanych: 0, gości: 1