2015-07-26 12 views
14

Mi sono appena imbattuto in uno strano comportamento utilizzando i metodi asincroni nelle strutture. Qualcuno può spiegare perché questo sta accadendo e, soprattutto, se c'è una soluzione alternativa? Ecco una semplice struttura di prova solo per il gusto di una manifestazione del problemaIl valore del campo privato di Struct non viene aggiornato utilizzando un metodo asincrono

public struct Structure 
{ 
    private int _Value; 

    public Structure(int iValue) 
    { 
     _Value = iValue; 
    } 

    public void Change(int iValue) 
    { 
     _Value = iValue; 
    } 

    public async Task ChangeAsync(int iValue) 
    { 
     await Task.Delay(1); 
     _Value = iValue; 
    } 
} 

Ora, usiamo la struttura e fare le seguenti chiamate

var sInstance = new Structure(25); 
sInstance.Change(35); 
await sInstance.ChangeAsync(45); 

La prima riga crea un'istanza della struttura e la sInstance._Value il valore è 25. La seconda riga aggiorna il valore sInstance._Value e diventa 35. Ora la terza riga non fa nulla ma mi aspetto che aggiorni il valore sInstance._Value a 45 ma lo sInstance._Value rimane 35. Perché? C'è un modo per scrivere un metodo asincrono per una struttura e modificare il valore di un campo di una struttura?

+8

Soluzione: non utilizzare le strutture mutabili con cui iniziare. Fondamentalmente, ho il sospetto che la tua struttura stia ricevendo un riquadrato per il verificarsi di callback e che la mutazione * stia accadendo, ma all'interno di quella casella. Ma davvero, non usare le strutture mutevoli. Questa è un'interessante * nuova * (per me) variazione su come essere morsi da loro, ma ci sono * un sacco * di trucchi come questo. –

+1

Interessante e fortemente correlato a [Come gestire gli effetti collaterali prodotti da async/attendi quando si tratta di tipi di valori mutabili?] (Http://stackoverflow.com/questions/24811287/). –

+0

Volevo sottolineare che rendere la struttura immutabile non è un'opzione nel mio caso. L'intero sistema è stato codificato molto tempo fa e dobbiamo solo estendere alcune parti con metodi asincroni. Riscrivere l'intera cosa non sembra fattibile a questo punto, ma capisco perfettamente il concetto di avere strutture per essere immutabili. C'è una soluzione alternativa più economica? –

risposta

15

Perché?

A causa del modo in cui il telefono struct viene sollevato sulla macchina a stati.

Questo è ciò che ChangeAsyncrealtà assomiglia:

[DebuggerStepThrough, AsyncStateMachine(typeof(Program.Structure.<ChangeAsync>d__4))] 
public Task ChangeAsync(int iValue) 
{ 
    Program.Structure.<ChangeAsync>d__4 <ChangeAsync>d__; 
    <ChangeAsync>d__.<>4__this = this; 
    <ChangeAsync>d__.iValue = iValue; 
    <ChangeAsync>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); 
    <ChangeAsync>d__.<>1__state = -1; 
    AsyncTaskMethodBuilder <>t__builder = <ChangeAsync>d__.<>t__builder; 
    <>t__builder.Start<Program.Structure.<ChangeAsync>d__4>(ref <ChangeAsync>d__); 
    return <ChangeAsync>d__.<>t__builder.Task; 
} 

La linea importante è questa:

<ChangeAsync>d__.<>4__this = this; 

Il compilatore solleva una copia della vostra struct nel suo stato macchina, aggiorna efficacemente la sua copia con il valore 45. Quando il metodo asincrono termina, ha modificato la copia, mentre l'istanza della tua struct rimane la stessa.

Questo è un po 'un comportamento previsto quando si tratta di strutture mutabili. Ecco perché sono tend to be evil.

Come si aggira questo? Dato che non vedo questo comportamento cambiare, dovrai creare uno class anziché uno struct.

Edit:

postato questo come un issue on GitHub. Ha ricevuto una risposta ben educato da @AlexShvedov, il che spiega un po 'più in profondità la complessità delle strutture e macchine a stati:

Dal momento che l'esecuzione di ogni chiusura può essere arbitrariamente in ritardo, abbiamo bisogno un modo per ritardare anche la vita di tutti i membri catturati nella chiusura . Non esiste un modo per farlo in generale per questo tipo di valore, poiché il tipo di valore può essere allocato sullo stack (variabili locali dei tipi di valore ) e lo spazio di stack verrà riutilizzato all'uscita dell'esecuzione del metodo.

In teoria, quando il tipo di valore è memorizzato come campo di alcuni oggetti/elementi di array gestiti , C# può emettere il codice di chiusura per eseguire la modifica della struttura in posizione.Sfortunatamente, non si sa dove si trova questo valore quando si emette il codice membro della struct, quindi C# ha deciso semplicemente per forzare gli utenti a gestire questa situazione manualmente (copiando il valore il più delle volte, come suggerito).