Quello che hai descritto è un eccellente esempio di ...
Una condizione di razza (applicabile sia all'elettronica che alla programmazione) è quando "il l'output e / o il risultato del processo dipendono in modo inaspettato e critico dalla sequenza o dai tempi di altri eventi. "
Hai anche descritto un problema con ...
In generale, i metodi che possono chiamarsi direttamente o indirettamente in modo sicuro sono re-entrant . Se i tuoi gestori di eventi stanno inavvertitamente innalzando altri eventi che non puoi gestire in modo sicuro, puoi dire di avere un codice non rientranti.
In C #, ecco un esempio di codice che mostra una condizione di competizione:
private void button1_Click(object sender, EventArgs e)
{
if (timer1.Enabled)
throw new InvalidOperationException("We're already ticking, you really should wait!");
timer1.Interval = 5000;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
MessageBox.Show("Okie Dokie");
timer1.Stop();
}
Ecco un semplice esempio di codice che presenta un problema di rientranza:
private void dataGridView1_CurrentCellChanged(object sender, EventArgs e)
{
//move to the next cell if we are in the first cell
if (dataGridView1.CurrentCell != null &&
dataGridView1.CurrentCell.RowIndex == 0 &&
dataGridView1.CurrentCell.ColumnIndex == 0)
{
//this throws an InvalidOperationException because the Microsoft DataGridView
//does not handle Re-entrant calls to SetCurrentCellAddressCore
dataGridView1.CurrentCell = dataGridView1[1, 0];
}
}
Esistono diversi modi per risolvere questi tipi di problemi. Due modi sono attraverso mutua esclusione (come con locking ) o evitando del tutto le condizioni della gara o il rientro.
Soluzione semplice per l'esempio n. 1 che utilizza l'esclusione reciproca:
private void button1_Click(object sender, EventArgs e)
{
if (timer1.Enabled)
return; //just silently ignore, maybe throw up a message box
timer1.Interval = 5000;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
MessageBox.Show("Okie Dokie");
timer1.Stop();
}
Angolo di Nitpicker: il blocco non è necessario in questo caso perché tutto il codice è in esecuzione sul thread dell'interfaccia utente.
Ecco una soluzione dell'esempio n. 2 che evita del tutto il problema:
private void button1_Click(object sender, EventArgs e)
{
Timer newTimer = new Timer() { Interval = 5000 };
newTimer.Tick += new EventHandler(newTimer_Tick);
newTimer.Start();
}
void newTimer_Tick(object sender, EventArgs e)
{
MessageBox.Show("Okie Dokie");
((Timer)sender).Dispose(); //probably not great to dispose in the middle of an eventhandler, but you get the idea...
}
Ecco una soluzione dell'esempio n. 3 che evita il problema della reintroduzione:
private void dataGridView1_CurrentCellChanged(object sender, EventArgs e)
{
//move to the next cell if we are in the first cell
if (dataGridView1.CurrentCell != null &&
dataGridView1.CurrentCell.RowIndex == 0 &&
dataGridView1.CurrentCell.ColumnIndex == 0)
{
//this does not throw an exception because
//the CurrentCell will be set some time later
dataGridView1.BeginInvoke(new MethodInvoker(() =>
{
dataGridView1.CurrentCell = dataGridView1[1, 0];
}));
}
}