none
SynchronizationContext verwenden in einen nicht UI Thread RRS feed

Alle Antworten

  • Hallo Gerry,

    grundsätzlich kann man auch eine eigene Klasse entwerfen, siehe z. B. den CodeProject Artikel:
    The .NET Framework's New SynchronizationContext Class

    dazu sollte man die dahinterstehenden Intentionen verstanden haben:
    Multithreadprogrammierung mit dem ereignisbasierten asynchronen Muster

    Gruß Elmar
    Freitag, 6. November 2009 10:09
    Beantworter
  • Hallo


    Habe jetzt diesen Timer erstellt mit AsyncOperation
    und habe ihn in einen nicht UI Thread instanziert, und er gibt keine Event vom erstellten Thread zurück.
    Da er wieder von einen nicht UI Thread zu einen nicht UI Thread synchronisiert werden soll.
    Er geht nur wenn ich ihn von einen Ui thread starte.

    Was mache ich falsch

    Public Class SynchTimer
        Inherits System.Timers.Timer
    
        Dim _AsyncOperation As AsyncOperation
        Public Event SynchTick()
    
        Sub New()
            Dim userSuppliedState As New Object
            _AsyncOperation = AsyncOperationManager.CreateOperation(userSuppliedState)
            AddHandler Me.Elapsed, AddressOf Tick
        End Sub
    
        Private Sub Tick()
            _AsyncOperation.Post(AddressOf Sub_Tick, Nothing)
        End Sub
    
        Sub Sub_Tick()
            RaiseEvent SynchTick()
        End Sub
    
    End Class
    Freitag, 6. November 2009 19:20
  • Hallo Gerry,

    leider kann ich Dir nicht ganz folgen. SynchronizationContext hat zunächst prinzipiell nichts mit UI Threads zu tun, du kannst die Klasse oder eine Ableitung der Klasse auch in anderen Zusammenhängen verwenden. Schau Dir doch mal bitte an, wo diese Klasse deklariert wird. Was ich aber sofort sehe, ist, dass deine von System.Timers.Timer abgeleitete Klasse den Konstruktor Public Sub New(ByVal interval As Double) der Basisklasse nicht implementiert. (Warum verwendest Du System.Timers.Timer? Soll die Komponente mit einem Element der Benutzeroberfläche verwenden werden? Wenn ja, dann schau Dir bitte das an: http://msdn.microsoft.com/de-de/library/system.timers.timer(VS.80).aspx).

    Damit ich das alles besser verstehe: Was genau soll deine von System.Timers.Timer abgeleitete Klasse leisten, wie willst Du diese einsetzen? Würde es etwas bringen, wenn Du statt _AsyncOperation.Post() _AsyncOperation.SynchronizationContext.Send() verwenden würdest? Wie Du siehst, rätsele ich ins Blaue hinein, weil ich nicht ganz verstehe, was Du vorhast.

     

     

    Gruß
    Marcel 

    Samstag, 7. November 2009 10:44
  • Ich muss eine (mehrere) Communication Programmieren ( SPS Communication usw.)
    Und da wollte ich denn Communications Aufbau in eine Thread auslagen und zusätzlich muss ich z.b. bei einer SPS Communication
    diese werte zyklisch auslesen (Sekundentakt circa).
    Und dieses zyklische auslesen wollte ich über einen Timer erledigen, aber leider geht der Forms Timer nicht in einen erstellten Thread
    und so muss ich, entweder den Thread Timer oder System Timer nehmen aber leider erzeugen dieser Timer im Tick event immer
    einen zweiten Thread.
    Und ich wollte eigenlich nur machen das das Tick Event in den Communication Thread ausgeführt wird.
    So das sich zur besseren Handhabung nur 2 Threads habe (UI Thread und Communications Thread) .
    Und leider gehen  diese ganzen synchronsierungsfunktionen nur von einen erstellten Thread in einen UI Thread, aber z.b. nicht
    von einen Timer Thread in einen erstellten Thread.
    Samstag, 7. November 2009 21:16
  • Wenn Du die ausgelesenen SPS-Werte vom Timer-Hintergrundthread an den UI-Thread zurückgegen willst, dann mußt Du entsprechend synchronisieren. Man könnte z.B. im Konstruktor der Klasse, die den Timer kapselt, SynchronisationContext ctxt = SynchronisationContext.Current schreiben, um dann im Timer_Tick-EventHandler ctxt.Post() zu verwenden und über ein abonniertes Ereignis die Meldung zurück an den UI-Thread zu geben. Sag mir bitte, wenn das nicht klar ist.
    Montag, 9. November 2009 07:43
  • Das habe ich aktuell auch gemacht, aber ich wollte nur vermeiden das ich mit 4 Thread's arbeiten muss.
    1UI Thread,  1 Communications Main Thread, 1 mal Timer Tick Thread (Lesen), 1 mal Timer Thread für Status Rückgabe.
    Und logischerweise wird es bei 4 Threads kompliziert.(Start stop Timer usw).
    So war mein gedanke das ich alles was mit Kommunication zu tun hatt, alles in meinen Erstellten Kommunikations Thread abläuft und von diesen Communications Thread Events Synchronisiert in denn UI Thread gefeuert werden.

    Also ist mein Problem nicht synchronisierung in den UI Thread, sondern die Synchronisierung der Timer Thread's zum Communications Thread.
    Weißt du hierzu eine Lösung.

    Danke

    Montag, 9. November 2009 08:18
  • Ich habe deinen Code weiter oben als Ausgangspunkt genommen. Mein Beispiel ist in C#, bin mir aber sicher, dass du keine Probleme haben wirst zu verstehen, was ich meine.

    using System;
    using System.Threading;
    
    namespace Device
    {
        public class IncomingValueEventArgs
        {
            public int Value { get; set; }
    
            public IncomingValueEventArgs(int value)
            {
                Value = value;
            }
        }
    
        public delegate void IncomingValueCallback(object sender, IncomingValueEventArgs e);
    
        class Scheduler
        {
            Timer  _timer;
            Thread _timerThread;
            object _state;
    
            SynchronizationContext _syncContext1;
    
            public event IncomingValueCallback ReportValue;
    
            public virtual void OnReportValue(IncomingValueEventArgs ea)
            {
                if (ReportValue != null)
                    ReportValue(null, ea);
            }
    
            public Scheduler()
            {
                _syncContext1 = SynchronizationContext.Current;
            }
    
            public void RunAsync()
            {
                _timerThread = new Thread(LaunchTimer);
                _timerThread.IsBackground = true;
                _timerThread.Start();
            }
            
            private void LaunchTimer()
            {
                _timer = new Timer(Tick, (object)_state, 0, 1000);
            }
    
            public void Stop()
            {
                _timer.Change(Timeout.Infinite, Timeout.Infinite);
                _timerThread.Abort();
            }
    
            private void Tick(object state)
            {
                _syncContext1.Post(ReadDevice1, state);
                //_syncContext1.Post(ReadDevice2, state);
            }
    
            private void ReadDevice1(object state)
            {
                OnReportValue(new IncomingValueEventArgs(Thread.CurrentThread.ManagedThreadIdd));
            }
        }
    }
    
    Ist es das, was du brauchst?
    • Bearbeitet Marcel Roma Montag, 9. November 2009 10:51
    Montag, 9. November 2009 09:28
  • Hallo

    Kann den Code im Moment leider nicht testen.
    Aber ich habe drübergeschaut und gesehen das du
    _syncContext1 = SynchronizationContext.Current; verwendest,
    und SynchronizationContext.Current habe ich schon mal in einen selbst erstellten versuch verwendet und es gibt null zurück
    wenn es in einen nicht Ui Thread verwendet wird.

    Und ich glaube das bei deinen Beispiel das event vom Timer in den UI Thread geworfen, aber nicht in den _timerThread.

    Denn ich würde sozusagen die ganzen Timer events im _timerThread sammeln und verarbeiten und dann von dort aus als Event in denn
    Ui Thred feuern.
    Montag, 9. November 2009 10:43
  • SynchronizationContext.Current habe ich schon mal in einen selbst erstellten versuch verwendet und es gibt null zurück wenn es in einen nicht Ui Thread verwendet wird.

    Ich beginne zu verstehen wo dein Problem liegt. Der folgende Code wird auf dem UI-Thread ausgeführt und startet den Timer-Thread. Dabei wird der aktuelle SynchronizationContext als Parameter übergeben:

            private void buttonRun_Click(object sender, EventArgs e)
            {
                Thread timerThread = new Thread(RunScheduler) { IsBackground = true };
                timerThread.Start(SynchronizationContext.Current);
            }
            private void RunScheduler(object uiSyncContext)
            {
                scheduler = new Scheduler();
                scheduler.ReportValue += scheduler_ReportValue;
                scheduler.RunAsync(uiSyncContext);
            }
    

    Auf dem Timer-Thread sind dann nur geringfügige Änderungen zum Code aus meinem vorherigen Post notwendig. Du synchronisierst auf dem Timer-Thread mittels _syncContext1 = new SynchronisationContext() und wenn du genug gesammelt hast und an den UI-Thread berichten willst, bedienst du dich einfach des übergebenen _syncContext2.

    using System;
    using System.Collections.Generic;
    using System.Threading;
    
    namespace Device
    {
        class Scheduler
        {
            Timer  _readingTimer;
            Timer  _uiUpdateTimer;
            Stack<int> _valuesStack;
    
            SynchronizationContext _syncContext1;
            SynchronizationContext _syncContext2;
    
            public event IncomingValueCallback ReportValue;
    
            public virtual void OnReportValue(IncomingValueEventArgs ea)
            {
                if (ReportValue != null)
                    ReportValue(null, ea);
            }
    
            public Scheduler()
            {
                _syncContext1 = new SynchronizationContext();
                _valuesStack = new Stack<int>();
            }
    
            public void RunAsync(object uiSyncContext)
            {
                _syncContext2 = uiSyncContext as SynchronizationContext;
                _readingTimer = new Timer(Tick, null, 0, 1000);
                _uiUpdateTimer = new Timer(UpdateUI, null, 3000, 3000);
            }
            
            private void LaunchTimer()
            {
            }
    
            private void UpdateUI(object state)
            {
                _syncContext2.Post(PopValuesFromStack, null);
            }
    
            private void PopValuesFromStack(object state)
            {
                while (_valuesStack.Count > 0)
                    OnReportValue(new IncomingValueEventArgs(_valuesStack.Pop()));
            }
    
            public void Stop()
            {
                _uiUpdateTimer.Change(Timeout.Infinite, Timeout.Infinite);
                _readingTimer.Change(Timeout.Infinite, Timeout.Infinite);
            }
    
            private void Tick(object state)
            {
                _syncContext1.Post(ReadDevice1, state);
                //_syncContext1.Post(ReadDevice2, state);
            }
    
            private void ReadDevice1(object state)
            {
                _valuesStack.Push(new Random().Next(0, 100));
            }
    
        }
    }
    
    Bei mir tut's.
    Montag, 9. November 2009 10:59
  • So meinte ich es ungefähr aber, Ich wollte eigentlich das der Timer in den von mir erstellten Communications Thread instanziert wird.
    So das er die events nicht direkt in denn UI Thread feuert sondern zuerst in den Communications Thread, und von dort in denn Ui Thread.
    Grund :

    Wenn ich mehrere Timer habe, dann kann ich sie zwar mit deinen Beispiel in einen gemeinsammen Thread Sammel, aber
    es ist der Ui Thread und da häng mir die Oberfläche.
    Und der Vorteil währe das die Variablen die Ich in den Timer Events verwende nicht auf Thread sicherheit überprüfen muss.

    Hast du jetzt verstanden was ich meinte?
    Montag, 9. November 2009 12:18
  • So meinte ich es ungefähr aber, Ich wollte eigentlich das der Timer in den von mir erstellten Communications Thread instanziert wird.
    So das er die events nicht direkt in denn UI Thread feuert sondern zuerst in den Communications Thread, und von dort in denn Ui Thread.
    Grund :

    Wenn ich mehrere Timer habe, dann kann ich sie zwar mit deinen Beispiel in einen gemeinsammen Thread Sammel, aber
    es ist der Ui Thread und da häng mir die Oberfläche.
    Und der Vorteil vom Communications Thread währe das die Variablen die Ich in den Timer Events verwende nicht auf Thread sicherheit überprüfen muss.

    Also
    1st UI thread
    1st CommunicationsThread erstellt mit new Thread (Dort die Timer instanzieren)
    1st Timer zyklisch lesen ,Event geht nach  CommunicationsThread und wird dort verarbeitet
    1st Timer SPS Status, event getht nach CommunicationsThread und wird dort verarbeitet

    Resultate aus allen thread's(CommunicationsThread ) mit Einen Event zum UI Thread

    Hast du jetzt verstanden was ich meinte?
    • Bearbeitet Gerry10 Montag, 9. November 2009 12:27
    Montag, 9. November 2009 12:22
  • So meinte ich es ungefähr aber, Ich wollte eigentlich das der Timer in den von mir erstellten Communications Thread instanziert wird.
    So das er die events nicht direkt in denn UI Thread feuert sondern zuerst in den Communications Thread, und von dort in denn Ui Thread.

    Genau das zeigt mein Code. Guck nochmal nach. Der/die Timer *werden* auf einem sekundären Thread (ich nannte ihn Timer-Thread, du nennst ihn Communications-Thread) erstellt, nicht auf dem UI-Thread!
    Montag, 9. November 2009 12:26
  • Entschuldigung hab s übersehen.
    Muss es dann testen.
    Bin gespannt ob   _syncContext1 = new SynchronizationContext(); erstellt in einen nicht ui Thread funktioniert.
    Ich hatte immer im kopf das SynchronizationContext überhaupt nur für einen  Ui Thread brauchbar ist.
    Montag, 9. November 2009 12:34
  • Gerry, theoretisch hast du recht mit deinen Befürchtungen. Aber für die Aktualisierung der Benutzeroberfläche ist nicht _syncContext1 sondern (na ja, keine Phantasie) _syncContext2 in UpdateUI() zuständig.

    Gruss
    Marcel

    Montag, 9. November 2009 12:39
  • mit syncContext 2 hattest du recht ;-)

    Ich bin beim SynchronizationContext einfach nachn diesen artikel gegangen, und dort stand das hier

    So Now, I Can Use SynchronizationContext to Sync Any Thread I Want, Right? Nope!

    At this point, you might try to use SynchronizationContext with any thread. However, you will soon find that your thread does not have a SynchronizationContext when using SynchronizationContext.Current, and it always returns null. No big deal you say, and you simply create a SynchronizationContext if there isn't one. Simple. But, it does not really work.

    http://www.codeproject.com/KB/threads/SynchronizationContext.aspx

    Montag, 9. November 2009 12:46
  • Eppur si muove ... Wie ich schon in meiner ersten Antwort zu diesem Thread sagte: SynchronizationContext hat zunächst prinzipiell nichts mit UI Threads zu tun (es ruft intern einfach ThreadPool.QueueUserWorkItem(new WaitCallback(d), state) auf, was mit der "Magie" des UI-Threads nichts zu tun hat).
    Montag, 9. November 2009 13:19
  • Habe jetzt getestet mit volgenden Code



    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Console.WriteLine("UI Thread " & " ID " & Thread.CurrentThread.ManagedThreadId)
            Dim timerThread As New Thread(AddressOf RunScheduler)
            timerThread.Start(SynchronizationContext.Current)
        End Sub
    
        Private Sub RunScheduler(ByVal uiSyncContext As Object)
            scheduler = New Scheduler()
            scheduler.RunAsync(uiSyncContext)
        End Sub
    
        Private Sub scheduler_ReportValue(ByVal s As Integer) Handles scheduler.ReportValue
            Console.WriteLine("UIEvent " & " ID " & Thread.CurrentThread.ManagedThreadId)
        End Sub
    Imports System
    Imports System.Collections.Generic
    Imports System.Threading
    
    Namespace Device
        Class Scheduler
            Private _readingTimer As Timer
            Private _uiUpdateTimer As Timer
    
    
            Private _syncContext1 As SynchronizationContext
            Private _syncContext2 As SynchronizationContext
            Dim i As Integer
            Public Event ReportValue(ByVal s As Integer)
    
    
            Public Overridable Sub OnReportValue(ByVal ea As Object)
                RaiseEvent ReportValue(ea)
            End Sub
    
            Public Sub New()
                _syncContext1 = New SynchronizationContext()
    
            End Sub
    
            Public Sub RunAsync(ByVal uiSyncContext As Object)
    
                Console.WriteLine("Thread ID " & Thread.CurrentThread.ManagedThreadId)
                _syncContext2 = TryCast(uiSyncContext, SynchronizationContext)
                _readingTimer = New Timer(AddressOf Tick, Nothing, 0, 1000)
                _uiUpdateTimer = New Timer(AddressOf UpdateUI, Nothing, 3000, 3000)
    
            End Sub
    
            Private Sub Tick(ByVal state As Object)
                Console.WriteLine("Timer Tick ID " & Thread.CurrentThread.ManagedThreadId)
                _syncContext1.Post(AddressOf ReadDevice1, state)
            End Sub
    
            Private Sub UpdateUI(ByVal state As Object)
                Console.WriteLine("Timer UpdateUI ID " & Thread.CurrentThread.ManagedThreadId)
                _syncContext2.Post(AddressOf PopValuesFromStack, Nothing)
            End Sub
    
            Private Sub PopValuesFromStack(ByVal state As Object)
                Console.WriteLine("PopValuesFromStack ID " & Thread.CurrentThread.ManagedThreadId)
                OnReportValue(i)
            End Sub
    
            Public Sub [Stop]()
                _uiUpdateTimer.Change(Timeout.Infinite, Timeout.Infinite)
                _readingTimer.Change(Timeout.Infinite, Timeout.Infinite)
            End Sub
    
            Private Sub ReadDevice1(ByVal state As Object)
                Console.WriteLine("ReadDevice1 ID " & Thread.CurrentThread.ManagedThreadId)
                i = i + 1
            End Sub
    
        End Class
    End Namespace

    Ausgabe:

    • UI Thread ID 11
    • Thread ID 12
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • Timer UpdateUI ID 8
    • PopValuesFromStack ID 11
    • UIEvent ID 11
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer UpdateUI ID 8
    • PopValuesFromStack ID 11
    • UIEvent ID 11
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer UpdateUI ID 8
    • PopValuesFromStack ID 11
    • UIEvent ID 11
    • Timer Tick ID 8
    • ReadDevice1 ID 8
    • Timer Tick ID 8
    • ReadDevice1 ID 8

    Müsste nicht

    ReadDevice1 ID 8 die ID 12 haben

    Bitte noch einmal um Hilfestellung.

    Anmerkung: Sollte man den Thread aus den Threadpool erstellen?
    weil du gesagt hast das SynchronizationContext  die funktion SThreadPool.QueueUserWorkItem vewendet. (war so ein gedanke)
    Danke

    Montag, 9. November 2009 17:18
  • Ja, es ist schon verdächtig, dass man bei 11 zu zählen beginnt um dann im Tick-Ereignishandler auf 8 zurückzuspringen.

    Das Framework benutzt den ThreadPool für die Timer aus dem Threading namespace. Es holt sich einfach den nächstbesten heraus, wenn es einen braucht. Muss aber nicht 8 und auch nicht unbedingt kleiner als die ID des UI-Threads sein. Wenn du im Tick-Ereignishandler deine Konsolenanweisung leicht abänderst, merkst du, was ich meine:

     

    Console.WriteLine(String.Format("Timer Tick ID {0}, from pool={1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread))




    Zu deiner zweiten Frage: Nein, wenn du SynchronizationContext nur dazu brauchst, Post() aufzurufen, kannst du auch gleich ThreadPool.QueueUserWorkItem() verwenden.

    Montag, 9. November 2009 22:17
  • Ich meinte auch  das es falsch ist wenn
    ReadDevice1 ID 8 hatt und nicht 12.
    Denn das heist das syncContext1 nicht functioniert.

    Denn syncContext1 wird mit Thread ID 12 erstellt.
    Aber syncContext1 zeigt bei ausführung dann bei Sub ReadDevice1 auf den thread ID 8.
    Das ist ja falsch,oder?

    Sehe ich das richtig , oder geht das nicht mehr weil Thread 12 gleich wieder geschlossen wird.

    und noch eine Frage , wieso sind beide Timer events schon vor der synchronisierung schon im gleichen thread,
    müssten sie nicht jeweils von einen separaten Thread sein?
    Montag, 9. November 2009 22:33
  • Hallo

    kann mir noch jemand eine Antwort auf die letzt Frage geben.

    Danke
    Freitag, 13. November 2009 13:19
  • Hallo,

    die Threads für die beiden Timer werden aus dem bestehenden ThreadPool gezogen und die Ereignis-Handler sind bei mir nicht im gleichen Thread. Arbeitest du auf einer uniprocessor-Maschine?

    Gruß
    Marcel
    Freitag, 13. November 2009 13:35
  • Habe Einen Pentium D 3Ghz

    Sollte ein echter DualCore sein

    Freitag, 13. November 2009 13:44
  • Ist es eine Konsolen-Anwendung (da gibt's Probleme mit Post())?
    Freitag, 13. November 2009 13:46
  • Nein, habe das Project auf Uploaded geladen
    http://ul.to/wfiw40
    Freitag, 13. November 2009 13:56
  • Das wird bei mir ausgegeben:

    Thread ID 11
    Timer Tick ID 7
    ReadDevice1 ID 7
    Timer Tick ID 13
    ReadDevice1 ID 13
    Timer Tick ID 13
    ReadDevice1 ID 13
    Timer Tick ID 7
    ReadDevice1 ID 7
    Timer UpdateUI ID 13
    PopValuesFromStack ID 10
    UIEvent ID 10
    Timer Tick ID 13
    ReadDevice1 ID 13
    Timer Tick ID 13
    ReadDevice1 ID 13
    Timer UpdateUI ID 13
    Timer Tick ID 7
    ReadDevice1 ID 7
    PopValuesFromStack ID 10
    UIEvent ID 10
    Timer Tick ID 7
    ReadDevice1 ID 7
    Timer Tick ID 7
    ReadDevice1 ID 13
    Timer UpdateUI ID 7
    Timer Tick ID 13
    ReadDevice1 ID 13
    PopValuesFromStack ID 10
    UIEvent ID 10
    Timer Tick ID 13
    ReadDevice1 ID 13

    Freitag, 13. November 2009 14:22
  • Also, auser PopValuesFromStack ID 10 (call UI Thread) stimmt von der synchronisierung usw.... überhaupt nichts mehr zusammen. :-(
    Jetzt habe ich keine Ahnung mehr ?!?!

    Freitag, 13. November 2009 14:41