Creiamo un progetto Windows Forms e aggiungiamo una label:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace TestSynchronizationContext
{
public class Form1 : Form
{
///
/// Required designer variable.
///
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Label label1;
public Form1()
{
InitializeComponent();
}
///
/// Clean up any resources being used.
///
///
true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(57, 101);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(474, 339);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
}
}
Quindi tasto destro sul progetto e aggiungiamo una nuova classe:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace TestSynchronizationContext
{
public class MyClass
{
public MyClass()
{
}
}
}
Questa classe avrà a disposizione un evento pubblico che sarà invocato attraverso un thread (secondario):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace TestSynchronizationContext
{
public class MyClass
{
public event EventHandler MyEvent;
private SynchronizationContext _synchronizationContext;
public MyClass()
{
_synchronizationContext = System.Threading.SynchronizationContext.Current;
Thread t = new Thread(raiseEvent);
t.IsBackground = true;
t.Start();
}
private void raiseEvent()
{
while (true)
{
if (MyEvent != null)
{
_synchronizationContext.Send(delegate
{
MyEvent(this, EventArgs.Empty);
}, null);
}
Thread.Sleep(1000);
}
}
}
}
Come vediamo dal codice riportato sopra, nel costruttore viene salvato in una variabile membro il contesto di sincronizzazione che è l'oggetto attraverso il quale redirigere l'evento dal thread secondario al thread principale,
La variabile "_synchronizationContext" viene inizializzata attraverso il contesto di sincronizzazione corrente, è quindi necessario che la classe MyClass sia creata dal thread di destinazione dell'evento MyEvent (nel nostro caso il thread che gestisce l'interfaccia grafica).
A questo punto nella finestra dell'applicazione dobbiamo creare la classe MyClass e collegarci all'evento MyEvent:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace TestSynchronizationContext
{
public class Form1 : Form
{
///
/// Required designer variable.
///
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Label label1;
private MyClass _myClass;
private int _index;
public Form1()
{
InitializeComponent();
_index = 0;
_myClass = new MyClass();
_myClass.MyEvent += myClass_MyEvent;
}
///
/// Clean up any resources being used.
///
///
true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(57, 101);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(474, 339);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private void myClass_MyEvent(object sender, EventArgs e)
{
label1.Text = String.Format("Event number {0}", _index);
_index++;
}
}
}
La variabile "_index" ha il solo scopo di conteggiare gli eventi ricevuti ed aggiornare il valore della proprietà "Text" della label inserita precedentemente nella finestra.
In questo modo non è quindi necessario (anzi non si deve) utilizzare la proprietà InvokeRequired per capire se l'invocazione arriva dal thread secondario o dal thread principale in quanto questa operazione è già fatta internamente alla classe che genera l'evento.
La classe SynchronizationContext permette di redirigere eventi attraverso i metodi "Send" e "Post" la loro differenza risiede nel fatto che il primo è sincrono mentre il secondo è asincrono e quindi non bloccante.
Nel caso in cui la classe MyClass fosse creata anch'essa da un thread secondario il problema si riconduce comunque nell'ottenere il contesto di sincronizzazione giusto su cui richiamare i metodi di sincronizzazione "Send" o "Post".