2016-06-17 14 views
10

C'è un'implementazione estesa di comando modello per il supporto multi-comandi (gruppi) in C#:Come condividere lo stesso contesto con thread diversi in Pattern multi-comando in C#?

var ctx= //the context object I am sharing... 

var commandGroup1 = new MultiItemCommand(ctx, new List<ICommand> 
    { 
     new Command1(ctx), 
     new Command2(ctx) 
    }); 

var commandGroup2 = new MultiItemCommand(ctx, new List<ICommand> 
    { 
     new Command3(ctx), 
     new Command4(ctx) 
    }); 

var groups = new MultiCommand(new List<ICommand> 
    { 
     commandGroup1 , 
     commandGroup2 
    }, null); 

Ora, l'esecuzione è come:

groups.Execute(); 

sto condividendo lo stesso contesto (ctx) oggetto.

Il piano di esecuzione dell'app Web deve separare i gruppi commandGroup1 e commandGroup2 in thread diversi. In particolare, commandGroup2 verrà eseguito in una nuova discussione e commandGroup1 nella discussione principale.

Esecuzione ora assomiglia:

//In Main Thread 
commandGroup1.Execute(); 

//In the new Thread 
commandGroup2.Execute(); 

come posso thread-sicurezza condividono la stessa context object (ctx), in modo da essere in grado di eseguire il rollback del commandGroup1 dal nuovo thread?

È t.Start(ctx); sufficiente o devo usare il lucchetto o qualcosa del genere?

Alcuni esempio di implementazione di codice è here

+0

Dipende da come viene usato quel ctx nei comandi. Se viene utilizzato contemporaneamente (ad esempio, entrambi i thread possono accedervi contemporaneamente), è possibile ad esempio bloccare la variabile ctx stessa in entrambi i comandi. In generale, la tua domanda non è molto chiara, forse sarà meglio se fornisci esempi concreti di come usi questi multicomandi. – Evk

+0

Hai esaminato la borsa concomitante? –

risposta

1

Si supponga di disporre di una classe MultiCommand che aggrega un elenco di ICommands e in qualche momento deve eseguire tutti i comandi in modo asincrono. Tutti i comandi devono condividere il contesto. Ogni comando può cambiare stato di contesto, ma non esiste un ordine impostato!

Il primo passo è dare il via a tutti i metodi ICommand Execute che passano nel CTX. Il prossimo passo è impostare un listener di eventi per le nuove modifiche CTX.

public class MultiCommand 
{ 
    private System.Collections.Generic.List<ICommand> list; 
    public List<ICommand> Commands { get { return list; } } 
    public CommandContext SharedContext { get; set; } 


    public MultiCommand() { } 
    public MultiCommand(System.Collections.Generic.List<ICommand> list) 
    { 
     this.list = list; 
     //Hook up listener for new Command CTX from other tasks 
     XEvents.CommandCTX += OnCommandCTX; 
    } 

    private void OnCommandCTX(object sender, CommandContext e) 
    { 
     //Some other task finished, update SharedContext 
     SharedContext = e; 
    } 

    public MultiCommand Add(ICommand cc) 
    { 
     list.Add(cc); 
     return this; 
    } 

    internal void Execute() 
    { 
     list.ForEach(cmd => 
     { 
      cmd.Execute(SharedContext); 
     }); 
    } 
    public static MultiCommand New() 
    { 
     return new MultiCommand(); 
    } 
} 

Ogni comando gestisce la parte asincrona simile a questo:

internal class Command1 : ICommand 
{ 

    public event EventHandler CanExecuteChanged; 

    public bool CanExecute(object parameter) 
    { 
     throw new NotImplementedException(); 
    } 

    public async void Execute(object parameter) 
    { 
     var ctx = (CommandContext)parameter; 
     var newCTX = await Task<CommandContext>.Run(() => { 
      //the command context is here running in it's own independent Task 
      //Any changes here are only known here, unless we return the changes using a 'closure' 
      //the closure is this code - var newCTX = await Task<CommandContext>Run 
      //newCTX is said to be 'closing' over the task results 
      ctx.Data = GetNewData(); 
      return ctx; 
     }); 
     newCTX.NotifyNewCommmandContext(); 

    } 

    private RequiredData GetNewData() 
    { 
     throw new NotImplementedException(); 
    } 
} 

Infine abbiamo istituito un sistema di gestore di eventi e la notifica comune.

public static class XEvents 
{ 
    public static EventHandler<CommandContext> CommandCTX { get; set; } 
    public static void NotifyNewCommmandContext(this CommandContext ctx, [CallerMemberName] string caller = "") 
    { 
     if (CommandCTX != null) CommandCTX(caller, ctx); 
    } 
} 

Ulteriori astrazioni sono possibili nella funzione di esecuzione di ciascun comando. Ma non lo discuteremo ora.

Ecco cosa questo disegno fa e non fa:

  1. Esso consente a qualsiasi compito finito per aggiornare il nuovo contesto sul filo era prima serie nella classe MultiCommand.
  2. Questo presuppone che non sia necessario lo stato basato sul flusso di lavoro. Il post si limitava a indicare che un gruppo di attività doveva essere eseguito solo in modo asincrono piuttosto che ordinato in modo asincrono.
  3. Nessun currencymanager è necessario perché ci stiamo affidando alla chiusura/completamento di ogni comando dell'attività asincrona per restituire il nuovo contesto sul thread che è stato creato!

Se è necessaria la concorrenza, ciò implica che lo stato del contesto è importante, che il design è simile a questo ma diverso. Quel disegno è facilmente implementato usando funzioni e callback per la chiusura.

+0

grazie mille. Questo è quello che stavo cercando. –

0

Fintanto ogni contesto viene usato solo da un singolo filo contemporaneamente non c'è problema con l'utilizzo da più thread.

+0

Puoi fornire un esempio su come condividere questo oggetto di contesto tra i thread? Basta usare t.Start (ctx); ? Devo usare il lucchetto? @ usr –

+0

E anche MultiItemCommand utilizza lo stesso oggetto cxx –

+0

È necessario assicurarsi che non vi siano usi concomitanti. Fallo in qualsiasi modo ti piaccia. Ad esempio: Task.Run(). Wait(); Task.Run(). Wait(); 'potrebbe utilizzare due thread ma non è simultaneo. Puoi bloccare anche se questo si adatta alla tua architettura. La condivisione tra i thread è la parte facile. Hai solo bisogno di passare l'oggetto ref al contesto circostante. – usr

2

Il codice di esempio fornito lascia sicuramente aperto un gran numero di domande sulla tua particolare situazione d'uso; tuttavia, cercherò di rispondere alla strategia generale per implementare questo tipo di problema per un ambiente multi-thread.

Il contesto oi relativi dati vengono modificati in modo accoppiato, non atmoico?

per esempio, sarebbe uno dei tuoi comandi fare qualcosa di simile:

Context.Data.Item1 = "Hello"; // Setting both values is required, only 
Context.Data.Item2 = "World"; // setting one would result in invalid state 

Quindi assolutamente si avrebbe bisogno di utilizzare lock(...) dichiarazioni da qualche parte nel codice. La domanda è dove.

Qual è il comportamento di sicurezza dei thread dei controller nidificati?

Nel codice di esempio GIST collegato, la classe CommandContext ha proprietà ServerController e ServiceController. Se non sei il proprietario di queste classi, allora devi attentamente controllare la documentazione sulla sicurezza del thread di queste classi.

Per esempio, se i comandi in esecuzione su due fili diversi eseguire chiamate come ad esempio:

Context.ServiceController.Commit(); // On thread A 

Context.ServiceController.Rollback(); // On thread B 

c'è una forte possibilità che queste due azioni non possono essere invocate contemporaneamente se il creatore della classe controller non si aspettava utilizzo multi-thread.

Quando per bloccare e cosa bloccare il

Prendere il blocco ogni volta che è necessario eseguire più azioni che devono accadere tutto o niente affatto, o quando si invoca operazioni di lunga durata che non si aspettano l'accesso simultaneo . Rilasciare il blocco il più presto possibile.

Inoltre, i blocchi devono essere eseguiti solo su proprietà o campi di sola lettura o costanti. Quindi, prima di fare qualcosa di simile:

lock(Context.Data) 
{ 
    // Manipulate data sub-properties here 
} 

Ricordate che è possibile scambiare l'oggetto che Data sta indicando. Il più sicuro implementazione è quello di fornire uno speciale oggetti di bloccaggio:

internal readonly object dataSyncRoot = new object(); 
internal readonly object serviceSyncRoot = new object(); 
internal readonly object serverSyncRoot = new object(); 

per ogni sotto-oggetto che richiede l'accesso esclusivo ed impiego:

lock(Context.dataSyncRoot) 
{ 
    // Manipulate data sub-properties here 
} 

non esiste una ricetta magica su quando e dove fare le serrature , ma in generale, più in alto nella pila delle chiamate li metti, più semplice e sicuro sarà il tuo codice, a scapito delle prestazioni - dal momento che entrambi i thread non possono più essere eseguiti contemporaneamente. Più in basso li posizioni, più il codice sarà simultaneo, ma anche più spese.

A parte: non c'è quasi nessuna penalità di prestazioni per l'effettivo prelievo e rilascio della serratura, quindi non c'è bisogno di preoccuparsi di ciò.

+0

Controllerò il tuo approccio e ti dirò. Grazie –

Problemi correlati