Come posso garantire l'atomicità di un'operazione get-and-set per reindirizzare Console.Out per l'uscita della console di registrazione?

StackOverflow https://stackoverflow.com/questions/367201

Domanda

Ho bisogno di intercettare il flusso di uscita della console (s) al fine di catturare per un registro, ma ancora passare attraverso le cose al flusso originale modo che l'applicazione funzioni correttamente. Questo significa ovviamente salvare il valore originario Console.Out TextWriter prima di cambiare con Console.SetOut(new MyTextWriterClass(originalOut)).

Presumo le singole operazioni per ottenere la proprietà Out e per chiamare il metodo setout () sono attuate da Console in un modo thread-safe. Ma mi piacerebbe fare in modo che qualche altro thread (ad es. L'esecuzione di codice dell'applicazione client che non mi controllo e non può pretendere di cambiare, e quindi non posso contare sul mio proprio schema di blocco personalizzato) non può cambiare accidentalmente tra il mio get e set e finiscono per essere sovrascritto da mio cambiamento ad esso (rompendo il comportamento del loro applicazione!). Dal momento che l'altro codice può semplicemente chiamare setout (), il mio codice dovrebbe idealmente ottenere lo stesso blocco utilizzato internamente da lock (Console) (supponendo che ci sia uno).

Purtroppo, <=> è un (statica) di classe, non un'istanza, così non si può solo <=>. E guardando nella documentazione di classe ci non sembra essere una qualsiasi menzione di bloccaggio. Questo non è l'utilizzo normalmente previsto di questi metodi Console, ma ci dovrebbe essere un modo sicuro di fare questo come un'operazione atomica.

In mancanza di un sistema di bloccaggio standard c'è qualche altro modo per garantire questo? Per un breve tratto critico (ed eseguita una sola volta), anche momentaneamente bloccando tutti gli altri thread potrebbe essere accettabile, se questo è l'unico modo per farlo. Stiamo utilizzando C # e .NET2.0.

Se nemmeno che è possibile (senza interrompere l'applicazione client), allora non ci resta che fare affidamento su di esso essendo molto improbabile che l'applicazione client potrebbe reindirizzare l'output della console e capita di farlo in tra la nostra get e set operazioni. Vorrei solo per coprire tutte le basi, per ogni evenienza.

Modifica : Ora che abbiamo una risposta concreta con l'esempio di codice, ho riformulato il titolo della domanda per più in generale riflettere i casi d'uso in cui la risposta (s) può aiutare, per essere più chiari . Inoltre, ha aggiunto un tag per "atomica".

È stato utile?

Soluzione

Se si guarda l'implementazione per setout Sembra thread-safe a me:

[HostProtection(SecurityAction.LinkDemand, UI=true)]
public static void SetOut(TextWriter newOut)
{
    if (newOut == null)
    {
        throw new ArgumentNullException("newOut");
    }
    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
    _wasOutRedirected = true;
    newOut = TextWriter.Synchronized(newOut);
    lock (InternalSyncObject)
    {
        _out = newOut;
    }
}

Modifica

La cosa cloest ad una soluzione che ho potuto venire con sta usando la riflessione per ottenere il loro InternalSyncObject e quindi bloccare su di esso.

Una parola di cautela, si tratta di una estremamente cattiva idea e deve essere utilizzato solo quando non esiste altra scelta. Si potrebbe causare il quadro di comportarsi unexpectadly e in crash il processo.

È inoltre necessario prestare attenzione a tutti i service pack e le versioni principali assicurandosi la variabile interna è ancora usato. Dal suo interno non v'è alcuna promessa che ci sarà nella prossima release. Scrivere il codice defensivley e cercare e degradare l'esperienza degli utenti ben si dovrebbe non l'oggetto con la riflessione.

Buona fortuna: -)

Altri suggerimenti

Ok, ora ho avuto un po 'di tempo per lavorare in realtà di un attuazione dell'approccio riflessione Josh suggerito. Il codice di base (nel mio ConsoleIntercepter classe, che eredita da TextWriter) è:

private static object GetConsoleLockObject()
{
    object lockObject;
    try
    {
        const BindingFlags bindingFlags = BindingFlags.GetProperty |
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
        // It's currently private, but we'd be happy if it were public, too.
        Type consoleType = typeof(Console);

        lockObject = consoleType.InvokeMember("InternalSyncObject", bindingFlags,
                                              null, null, null);
    }
    catch
    {
        lockObject = null; // Return null on any failure.
    }
    return lockObject;
}
public static void RegisterConsoleIntercepter()
{
    object lockObject = GetConsoleLockObject();
    if (lockObject != null)
    {
        // Great!  We can make sure any other changes happen before we read
        // or after we've written, making this an atomic replacement operation.
        lock (lockObject)
        {
            DoIntercepterRegistration();
        }
    }
    else
    {
        // Couldn't get the lock object, but we still need to work, so
        // just do it without an outer lock, and keep your fingers crossed.
        DoIntercepterRegistration();
    }
}

Poi il DoIntercepterRegistration() sarebbe qualcosa come:

private static void DoIntercepterRegistration()
{
    Console.SetOut(new ConsoleIntercepter(Console.Out));
    Console.SetError(new ConsoleIntercepter(Console.Error));
}

Questo è solo per la sostituzione atomica lock-protetta di e l'errore; ovviamente l'intercettazione reale, la manipolazione, e la trasmissione alla precedente <=> s richiede codice aggiuntivo, ma che non è parte di questa domanda.

Si noti che (nel codice sorgente fornito per .NET2.0) la classe Console utilizza InternalSyncObject per proteggere l'inizializzazione di Out ed errori e per proteggere setout () e SetError (), ma non usa il blocco attorno legge di e l'errore una volta che sono stati precedentemente inizializzato. Questo è sufficiente per il mio caso particolare, ma potrebbe avere le violazioni atomicitiy se hai fatto qualcosa di più complesso (e folle); Non riesco a pensare a nessun scenari utili con questo problema, ma quelli volutamente patologici può essere immaginato.

Se si può fare questo presto Main() avrete molte più possibilità di evitare eventuali condizioni di gara, soprattutto se è possibile farlo prima cosa crea un thread di lavoro. O nel costruttore statico di una classe.

Se [STAThread] è contrassegnato con l'attributo <=> sarà in esecuzione in un singolo appartamento filettata in modo che dovrebbe aiutare anche.

In generale, vorrei utilizzare il modello di sicurezza per mantenere il codice client da reindirizzare la console mentre non siete in cerca. Sono sicuro che richiede la piena fiducia, quindi, se il codice del client è in esecuzione con attendibilità parziale, non sarà in grado di chiamare setout ().

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top