Jak sprawdzać istnienie nowej wiadomości co 5 sekund w kilku wątkach?

0

Dzień dobry,
mam utworzoną poniższą metodę:

public  void SprawdCzyNowaWiadomosc(ToolStripStatusLabel kontrolka)
{
    int licznik = 0;
    licznik = Convert.ToInt32(Polecenia.WykonajSQLSelect("SELECT COUNT(*) FROM komunikacja WHERE status_przeczytania = 0 AND iduzytkownik = " + Polecenia.PobierzAktualneIDUzytkownika()));
    if (licznik > 0)
    {
        kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
        kontrolka.ForeColor = Color.Red;
        kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
        kontrolka.Text = " - Masz nową wiadomość -";
      //  Thread.Sleep(5000);
    }
    else
    {
        kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
        kontrolka.ForeColor = Color.Black;
        kontrolka.Text = " - Brak nowej wiadomości -";
    }  
}

którą wykonuje w głównej metodzie za pomocą poniższego kodu:

System.Threading.Tasks.Task.Factory.StartNew(() =>
  {
      System.Threading.Thread.Sleep(5000);
      SprawdCzyNowaWiadomosc(status_nowej_wiadomosci);
  });

Celem powyższego kodu jest uruchomienie w oddzielnym wątku metody SprawdCzyNowaWiadomosc i powtarzanie jej co 5 sekund.
Jednak podczas uruchomienia programu w trybie debugowania wyświetla mi się komunikat:
System.InvalidOperationException: Nieprawidłowa operacja między wątkami: do formantu 'Kontrolka_Zalogowany_Uzytkownik uzyskiwany jest dostęp z wątku innego niż wątek, w którym został utworzony.

Błąd wystąpił przy tej linijce kodu:

kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
2

Dostęp do elementów UI jest możliwy tylko z wątku na którym został dany element utworzony.
Wykonujesz metodę SprawdCzyNowaWiadomosc na nowym wątku, a w środku tej metody przypisujesz wartości do pól na kontrolce która została utworzona na innym wątku.

Możesz zostawić w tej metodzie fragment:

int licznik = 0;
licznik = Convert.ToInt32(Polecenia.WykonajSQLSelect("SELECT COUNT(*) FROM komunikacja WHERE status_przeczytania = 0 AND iduzytkownik = " + Polecenia.PobierzAktualneIDUzytkownika()));

natomiast wszystkie przypisania które masz w if-else nie mogą być wykonane na nowym wątku.

Możesz uzyć klasy Dispatcher, aby wykonać przypisania na głównym wątku:

public  void SprawdCzyNowaWiadomosc(ToolStripStatusLabel kontrolka)
{
    int licznik = 0;
    licznik = Convert.ToInt32(Polecenia.WykonajSQLSelect("SELECT COUNT(*) FROM komunikacja WHERE status_przeczytania = 0 AND iduzytkownik = " + Polecenia.PobierzAktualneIDUzytkownika()));

    Dispatcher.Invoke(() =>
    {
      if (licznik > 0)
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
          kontrolka.ForeColor = Color.Red;
          kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
          kontrolka.Text = " - Masz nową wiadomość -";
        //  Thread.Sleep(5000);
      }
      else
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
          kontrolka.ForeColor = Color.Black;
          kontrolka.Text = " - Brak nowej wiadomości -";
      }
    });
}
0
AbcDefGhi napisał(a):

Dostęp do elementów UI jest możliwy tylko z wątku na którym został dany element utworzony.
Wykonujesz metodę SprawdCzyNowaWiadomosc na nowym wątku, a w środku tej metody przypisujesz wartości do pól na kontrolce która została utworzona na innym wątku.

Możesz zostawić w tej metodzie fragment:

int licznik = 0;
licznik = Convert.ToInt32(Polecenia.WykonajSQLSelect("SELECT COUNT(*) FROM komunikacja WHERE status_przeczytania = 0 AND iduzytkownik = " + Polecenia.PobierzAktualneIDUzytkownika()));

natomiast wszystkie przypisania które masz w if-else nie mogą być wykonane na nowym wątku.

Możesz uzyć klasy Dispatcher, aby wykonać przypisania na głównym wątku:

public  void SprawdCzyNowaWiadomosc(ToolStripStatusLabel kontrolka)
{
    int licznik = 0;
    licznik = Convert.ToInt32(Polecenia.WykonajSQLSelect("SELECT COUNT(*) FROM komunikacja WHERE status_przeczytania = 0 AND iduzytkownik = " + Polecenia.PobierzAktualneIDUzytkownika()));

    Dispatcher.Invoke(() =>
    {
      if (licznik > 0)
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
          kontrolka.ForeColor = Color.Red;
          kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
          kontrolka.Text = " - Masz nową wiadomość -";
        //  Thread.Sleep(5000);
      }
      else
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
          kontrolka.ForeColor = Color.Black;
          kontrolka.Text = " - Brak nowej wiadomości -";
      }
    });
}

Jeszcze mam taki komunikat:
screenshot-20231123232524.png

0

Tak się kończy niepodawanie nazwy technologii, w której się pisze.

0
somekind napisał(a):

Tak się kończy niepodawanie nazwy technologii, w której się pisze.

C# / Winforms :)

1
virusek391 napisał(a):
somekind napisał(a):

Tak się kończy niepodawanie nazwy technologii, w której się pisze.

C# / Winforms :)

Sprawdzone na .NET Framework 4.8. Klasa Dispatcher jest w bibliotece WindowsBase.dll w namespace System.Windows.Threading.

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(() =>
{
    if (licznik > 0)
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
          kontrolka.ForeColor = Color.Red;
          kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
          kontrolka.Text = " - Masz nową wiadomość -";
        //  Thread.Sleep(5000);
      }
      else
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
          kontrolka.ForeColor = Color.Black;
          kontrolka.Text = " - Brak nowej wiadomości -";
      }
});
0
AbcDefGhi napisał(a):
virusek391 napisał(a):
somekind napisał(a):

Tak się kończy niepodawanie nazwy technologii, w której się pisze.

C# / Winforms :)

Sprawdzone na .NET Framework 4.8. Klasa Dispatcher jest w bibliotece WindowsBase.dll w namespace System.Windows.Threading.

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(() =>
{
    if (licznik > 0)
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
          kontrolka.ForeColor = Color.Red;
          kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
          kontrolka.Text = " - Masz nową wiadomość -";
        //  Thread.Sleep(5000);
      }
      else
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
          kontrolka.ForeColor = Color.Black;
          kontrolka.Text = " - Brak nowej wiadomości -";
      }
});

To już chwila bo w projekcie używam wersji 4.7.2 zaraz sobie to zmienię.

0
AbcDefGhi napisał(a):
virusek391 napisał(a):
somekind napisał(a):

Tak się kończy niepodawanie nazwy technologii, w której się pisze.

C# / Winforms :)

Sprawdzone na .NET Framework 4.8. Klasa Dispatcher jest w bibliotece WindowsBase.dll w namespace System.Windows.Threading.

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(() =>
{
    if (licznik > 0)
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
          kontrolka.ForeColor = Color.Red;
          kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
          kontrolka.Text = " - Masz nową wiadomość -";
        //  Thread.Sleep(5000);
      }
      else
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
          kontrolka.ForeColor = Color.Black;
          kontrolka.Text = " - Brak nowej wiadomości -";
      }
});

screenshot-20231123235418.png

0
virusek391 napisał(a):

Dzień dobry,
mam utworzoną poniższą metodę:

       public  void SprawdCzyNowaWiadomosc(ToolStripStatusLabel kontrolka)
        {
                int licznik = 0;
                licznik = Convert.ToInt32(Polecenia.WykonajSQLSelect("SELECT COUNT(*) FROM komunikacja WHERE status_przeczytania = 0 AND iduzytkownik = " + Polecenia.PobierzAktualneIDUzytkownika()));
                if (licznik > 0)
                {
                    kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
                    kontrolka.ForeColor = Color.Red;
                    kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
                    kontrolka.Text = " - Masz nową wiadomość -";
                  //  Thread.Sleep(5000);
                }
                else
                {
                    kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
                    kontrolka.ForeColor = Color.Black;
                    kontrolka.Text = " - Brak nowej wiadomości -";
                }
           
        }

którą wykonuje w głównej metodzie za pomocą poniższego kodu:

   System.Threading.Tasks.Task.Factory.StartNew(() =>
            {
                System.Threading.Thread.Sleep(5000);
                SprawdCzyNowaWiadomosc(status_nowej_wiadomosci);
            });

Celem powyższego kodu jest uruchomienie w oddzielnym wątku metody SprawdCzyNowaWiadomosc i powtarzanie jej co 5 sekund.
Jednak podczas uruchomienia programu w trybie debugowania wyświetla mi się komunikat:
System.InvalidOperationException: Nieprawidłowa operacja między wątkami: do formantu 'Kontrolka_Zalogowany_Uzytkownik uzyskiwany jest dostęp z wątku innego niż wątek, w którym został utworzony.

Błąd wystąpił przy tej linijce kodu:

    kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;

Powiem wam tak, wrzuciłem w Chatgpt takie słowa kluczowe jak ToolStripStatusLabel dispacher trhreading c#
i na podstawie wcześniejszej metody sprawdzczynowawiadomosc zbudował mi algorytm, który działa u mnie i nie ma już błędu podczas debugowania:

public void SprawdCzyNowaWiadomosc(ToolStripStatusLabel kontrolka)
{
    int licznik = 0;
    string sqlQuery = "SELECT COUNT(*) FROM komunikacja WHERE status_przeczytania = 0 AND iduzytkownik = " + Polecenia.PobierzAktualneIDUzytkownika();
    licznik = Convert.ToInt32(Polecenia.WykonajSQLSelect(sqlQuery));

    Action updateUI = () =>
    {
        if (licznik > 0)
        {
            kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
            kontrolka.ForeColor = Color.Red;
            kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
            kontrolka.Text = " - Masz nową wiadomość -";
            // Thread.Sleep(5000);
        }
        else
        {
            kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
            kontrolka.ForeColor = Color.Black;
            kontrolka.Text = " - Brak nowej wiadomości -";
        }
    };

    if (kontrolka.Owner.InvokeRequired)
    {
        kontrolka.Owner.BeginInvoke(updateUI);
    }
    else
    {
        updateUI();
    }
}
0
virusek391 napisał(a):
AbcDefGhi napisał(a):
virusek391 napisał(a):
somekind napisał(a):

Tak się kończy niepodawanie nazwy technologii, w której się pisze.

C# / Winforms :)

Sprawdzone na .NET Framework 4.8. Klasa Dispatcher jest w bibliotece WindowsBase.dll w namespace System.Windows.Threading.

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(() =>
{
    if (licznik > 0)
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
          kontrolka.ForeColor = Color.Red;
          kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
          kontrolka.Text = " - Masz nową wiadomość -";
        //  Thread.Sleep(5000);
      }
      else
      {
          kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
          kontrolka.ForeColor = Color.Black;
          kontrolka.Text = " - Brak nowej wiadomości -";
      }
});

To już chwila bo w projekcie używam wersji 4.7.2 zaraz sobie to zmienię.

Dzięki za pomoc :)

2

No bo ten Dispatcher to jest WPF, a nie WinForms. :P

WinForms ma swoje rozwiązania, o ile dobrze pamiętam, to mniej więcej takie:

if (kontrolka.InvokeRequired)
{
    kontrolka.Invoke(() => kontrolka.Text = text);
}
else
{
    kontrolka.Text = text;
}
0
somekind napisał(a):

No bo ten Dispatcher to jest WPF, a nie WinForms. :P

WinForms ma swoje rozwiązania, o ile dobrze pamiętam, to mniej więcej takie:

if (kontrolka.InvokeRequired)
{
    kontrolka.Invoke(() => kontrolka.Text = text);
}
else
{
    kontrolka.Text = text;
}

A powiedz mi jeżeli teraz jeden element mojego projektu podniesiony jest do NET. Framework 4.8 to pozostałem elementy też podnieść, czy też jeżeli działają na 4.7.2 i nie korzystają z funkcji które dostępne są wyłącznie w wersji 4.8 to nie ma potrzeby upgradu?

1

Nie da się podnieść jednego elementu projektu, da się podnieść cały projekt (w ramach solucji).

0
somekind napisał(a):

Nie da się podnieść jednego elementu projektu, da się podnieść cały projekt (w ramach solucji).

Chodzi mi oto:

Application->Target Framework

w większości mam target ustawiony na 4.7.2, a tylko do jednego projektu ustawiony mam teraz 4.8

2
virusek391 napisał(a):

Celem powyższego kodu jest uruchomienie w oddzielnym wątku metody SprawdCzyNowaWiadomosc i powtarzanie jej co 5 sekund.

To nie jest jej cel, tylko implementacja.

Celem, jak rozumiem, jest powiadomienie użytkownika o nowej wiadomości.

1
virusek391 napisał(a):

w większości mam target ustawiony na 4.7.2, a tylko do jednego projektu ustawiony mam teraz 4.8

No jak działa, to nie trzeba zmieniać. Jak nie działa, to bym zmienił.

0

Możesz też użyć SynchronizationContext który opakowuje odpowiednio calle do Dispatcher lub Invoke przez WindowsFormsSynchronizationContext, DispatcherSynchronizationContext czy AspNetSynchronizationContext i niezależnie czego używasz będzie działał ten sam kod.

Lub po prostu możesz to zrobić na async / await które pod spodem użyje SynchronizationContextu i po prostu wykona kod na głównym wątku (a raczej na tym na którym zacząłeś).

Możesz do tego użyć timera który jest odpalany synchronicznie - Timer z System.Windows.Forms a w nim tylko odpalać zapytanie SQL asynchronicznie:

var licznik = await SprawdCzyNowaWiadomosc();
if (licznik > 0)
    {
        kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
        kontrolka.ForeColor = Color.Red;
        kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
        kontrolka.Text = " - Masz nową wiadomość -";
      //  Thread.Sleep(5000);
    }
    else
    {
        kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
        kontrolka.ForeColor = Color.Black;
        kontrolka.Text = " - Brak nowej wiadomości -";
    }

moim zdaniem dużo przejrzyściej, bez żadnych invoke'ów itp.

Od .NET 6 można użyć kolejnego Timera w .NET - PeriodicTimer.

somekind napisał(a):

WinForms ma swoje rozwiązania, o ile dobrze pamiętam, to mniej więcej takie:

if (kontrolka.InvokeRequired)
{
    kontrolka.Invoke(() => kontrolka.Text = text);
}
else
{
    kontrolka.Text = text;
}

Nigdy nie rozumiałem tego snippetu, a spotkałem go setki razy. Po cholerkę sprawdzać InvokeRequired skoro wiemy że kod zawsze będzie wykonany z innego wątku i else nigdy się nie wykona. W mało prawdopodobnym scenariuszu (oznaczającym zazwyczaj burdel w kodzie) gdzie ten sam kod może być wywołany z wątku głównego lub innego, nic nie zaszkodzi wywołać .Invoke.

Poza tym zamiast spamować bazę zapytaniami może lepiej użyć message brokera lub eventów na bazie danych jeśli silnik je wspiera. Wtedy w ogóle nie potrzeba timera

0

Na Twoim miejscu zainteresowałbym się: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/detecting-changes-with-sqldependency

Puszczanie zapytań co 5 sekund do bazy to nie najlepszy sposób na sledzenie zmian

0
obscurity napisał(a):

Możesz też użyć SynchronizationContext który opakowuje odpowiednio calle do Dispatcher lub Invoke przez WindowsFormsSynchronizationContext, DispatcherSynchronizationContext czy AspNetSynchronizationContext i niezależnie czego używasz będzie działał ten sam kod.

Lub po prostu możesz to zrobić na async / await które pod spodem użyje SynchronizationContextu i po prostu wykona kod na głównym wątku (a raczej na tym na którym zacząłeś).

Możesz do tego użyć timera który jest odpalany synchronicznie - Timer z System.Windows.Forms a w nim tylko odpalać zapytanie SQL asynchronicznie:

var licznik = await SprawdCzyNowaWiadomosc();
if (licznik > 0)
    {
        kontrolka.Image = global::Glowne_Okno.Properties.Resources.nowa_wiadomosc;
        kontrolka.ForeColor = Color.Red;
        kontrolka.BorderStyle = System.Windows.Forms.Border3DStyle.Raised;
        kontrolka.Text = " - Masz nową wiadomość -";
      //  Thread.Sleep(5000);
    }
    else
    {
        kontrolka.Image = global::Glowne_Okno.Properties.Resources.brak_nowej_wiadomosci;
        kontrolka.ForeColor = Color.Black;
        kontrolka.Text = " - Brak nowej wiadomości -";
    }

moim zdaniem dużo przejrzyściej, bez żadnych invoke'ów itp.

Od .NET 6 można użyć kolejnego Timera w .NET - PeriodicTimer.

somekind napisał(a):

WinForms ma swoje rozwiązania, o ile dobrze pamiętam, to mniej więcej takie:

if (kontrolka.InvokeRequired)
{
    kontrolka.Invoke(() => kontrolka.Text = text);
}
else
{
    kontrolka.Text = text;
}

Nigdy nie rozumiałem tego snippetu, a spotkałem go setki razy. Po cholerkę sprawdzać InvokeRequired skoro wiemy że kod zawsze będzie wykonany z innego wątku i else nigdy się nie wykona. W mało prawdopodobnym scenariuszu (oznaczającym zazwyczaj burdel w kodzie) gdzie ten sam kod może być wywołany z wątku głównego lub innego, nic nie zaszkodzi wywołać .Invoke.

Poza tym zamiast spamować bazę zapytaniami może lepiej użyć message brokera lub eventów na bazie danych jeśli silnik je wspiera. Wtedy w ogóle nie potrzeba timera

Dzięki za pomoc :)

Panczo napisał(a):

Na Twoim miejscu zainteresowałbym się: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/detecting-changes-with-sqldependency

Puszczanie zapytań co 5 sekund do bazy to nie najlepszy sposób na sledzenie zmian

Chodzi to aby co 5 sekund sprawdzał w bazie SQL czy nie ma nowych wiadomości.

1
virusek391 napisał(a):

Chodzi to aby co 5 sekund sprawdzał w bazie SQL czy nie ma nowych wiadomości.

Tak wiemy o co ci chodzi, natomiast pytanie co 5 sekund bazy danych to nie jest jedyny sposób żeby wiedzieć czy są nowe wiadomości. Przeczytaj jeszcze raz sugestie.

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