Stasera ripensavo alla chiaccherata fatta con Raffaele durante il tragitto "nevoso" verso la pizzeria di San Felice la sera del workshop, a propostio come sta la testa?, riguardo all'implementazione di ISyncronizeInvoke. Il problema concreto è (forse era) questo:

Ho una collection che implementa tra le tante cose IBindingList questa collection è una collection di classi (in definitiva dei WorkerThread) che a loro volta implementano INotifyPropertyChanged il problema è che una delle proprietà viene aggiornata da un Thread diverso da quello che ha creato la classe, se quindi mettiamo in binding la collection con ad esempio un DataGridView ci becchiamo un sacrosanta exception che ci informa che non possiamo accedere alla UI da un Thread diverso da quello che ha creato i controlli, bene doveroso .

Faccio un po' di ricerche e non cavo un ragno dal buco, poi posto sui NG MS e mi rispondono Andrea Boschin e Raffaele Rialdi, il primo suggerendomi di dare un'occhiata all'implemetazione del BackgroundWorker per la versione 1.1 e il secondo postandomi un link al sito di IDesign  dove trovo l'articolo che ha ispirato colui che ha realizzato il BackgroundWorker per la 1.1, una prima possibile soluzione è quindi questa (è uno snippet scritto al volo in IMHO...):

void SafeFire( Delegate del )
{
    Delegate[] invocationList = del.getInvocationList();
    
foreach( Delegate d in invocationList )
    {
        
if( d.Target != null )
        {
            
//Se c'è un handler procediamo
            
ISynchronizeInvoke isi = d.Target as ISynchronizeInvoke;
            
if( isi != null && isi.InvokeRequired )
            {
                isi.Invoke( del, /* ...args... */ );
            }
            
else
            
{
                del.DynamicInvoke( 
/* ...args... */ );
            }
        }
    }
}

Fatte un po' di prove il problema persiste perchè alla fine il Target nel mio caso è sempre e solo il CurrencyManager che gestisce i dati quindi non sarà mai ISynchronizeInvoke

Stasera durante il mio girovagare su MSDN trovo le due classi del titolo del post, faccio un po' di prove e sembrano la manna dal cielo, un esempio per capirci al volo:

class MyAsyncClass
{
    AsyncOperation operation;
    SendOrPostCallback safeCallback;
    MyForm frm;

    
public MyAsyncClass( MyForm frm )
    {
        
/*
            Creo un "safe" delegate
        */
        
this.safeCallback = new SendOrPostCallback( SafeCallback );
        
        
/*
            Mi segno un riferimento alla form chiamante
            questa è una bruttura ma per l'esempio va
            bene così 
            Non sparate sul "pianista"!
        */
        
this.frm = frm;
    }

    
public void Start()
    {
        
/*
            Mi faccio dare dall'AsyncOperationManager una nuova istanza
            di AsyncOperation la classe che sotto sotto gestisce la 
            sincronizzazione tra i thread
        */
        
this.operation = AsyncOperationManager.CreateOperation( null );
        
        
/*
            Creo un thread
            e lo faccio partire
        */
        
Thread t = new Thread( new ThreadStart( WT ) );
        t.Start();
    }

    
void WT()
    {
        
for( Int32 i = 1; i <= 10; i++ )
        {
            
/*
                Simulo un'operazione lunga
            */
            
Thread.Sleep( 1000 );
            
            
/*
                La chiave di tutto, chiedo alla classe AsyncOperation
                di postare un nuovo "evento" utilizzando il mio
                delegato "safeCallback"
            */
            
this.operation.Post( this.safeCallback, ( i * 10 ) );
        }
    }
    
    
void SafeCallback( object arg )
    {
        
/*
            Questo viene eseguito nel thread
            corretto, infatti possiamo accedere
            ai controlli senza che il fx se la prenda
            a male :-)
        */
        
Int32 c = ( Int32 )arg;
        
this.frm.MyLabel.Text = "Hi! " + c.ToString();
    }
}

MyAsyncClass altro non è che un grezzissimo BackgroundWorker che scatena gli eventi in maniera ThreadSafe, indagando ulteriormente si scopre che il fx stesso fa larghissimo uso di questo sistema (l'accoppiata AsyncOperationManager / AsyncOperation) ad esempio ovunque ci sia il nuovo pattern per le operazioni asincrone DoOperationAsync / DoOperationCompleted, è il caso ad esempio dellla PictureBox che espone un nuovo metodo LoadAsync (per caricare un imagine in maniera asincrona) e il corrispondente evento LoadCompleted che sarà scatenato nel Thread corretto in cui la PictureBox è stata creata con tutti i vantaggi che questo comporta.

Adesso non mi resta che proseguire con lo studio del mio scenario e capire se devo costrurimi un mio ThreadSafeCurrencyManager (che internamente gestisca gli eventi in questo modo) o se quello che ho già esposto è più che sufficiente.

Vi terrò aggiornati

.m

powered by IMHO 1.3