View Full Version : [.net]Coda(FIFO), EF e 'timer'...
RaouL_BennetH
24-09-2012, 20:09
Ariekkime ... credetemi, mi piacerebbe tanto essere uno di quelli che i problemi li risolve anzichè chiedere sempre aiuto.... btw...
Ho una tabella mappata su EF che contiene valori come questi:
id=21; fkID=15; startJob = 1; jobLenght = 100; isLast = 0; workerID = 10;
id=22; fkID=15; startJob = 101; jobLenght = 100; isLast = 0; workerID = 19;
id=23; fkID=15; startJob = 201; jobLenght = 0; isLast = 1; workerID = 61;
Ogni "worker" ha un terminale di lavorazione, sul quale, quando viene passato un "ordine di lavoro", devo rappresentare la lista dei prodotti da lavorare e fare in modo che a ciascun "worker" appaia la lista in base ai valori presenti in startJob per la durata di jobLenght.
Per fare un esempio:
1) Viene inserito un ordine
2) i prodotti che sono nell'ordine hanno una lavorazione
3) la lavorazione ha varie fasi
4) la prima fase di lavorazione viene inviata all'operatore preposto
5) la seconda fase di lavorazione, deve durare 'jobLenght' prima di essere
inviata sul terminale dell'altro eventuale operatore preposto
6) ... e così via fin quando non si arriva all'ultima fase che valorizza il campo "isLast" a 1 e chiude quindi la lavorazione del prodotto.
L'ordine di inserimento e di uscita dei vari "ordini" è di tipo FIFO.
Avevo quindi pensato di creare un business object con i campi che mi interessano e gestire una o più "Queue<T>".
Ho capito che devo gestire una sorta di "timer" o di metodo System.Threading.Sleep... che prenda il valore di jobLenght, ma non riesco a 'rappresentarmelo' come codice..
Mi dareste qualche dritta ?
Grazie sempre.
RaouL.
RaouL_BennetH
25-09-2012, 21:23
sono riuscito ad implementare quasi tutto (la coda e i vari eventi ad essa legata).
Mi manca soltanto un ultimo pezzettino ma che davvero non ho idea di come implementare e avrei davvero bisogno di aiuto:
Non riesco neanche a pensare come "scalare" di posizione fra una fase e l'altra ed a far partire un "timer" che in base alla durata della fase invii i dati fra un terminale e l'altro :cry:
tomminno
26-09-2012, 08:56
Io lo vedevo un lavoro perfetto per Workflow Foundation.
Non ho capito la tua ultima richiesta. Cosa intendi di preciso per "inviare i dati fra un terminale e l'altro"?
Questi terminali non leggono le informazioni da un db? Ricevono una qualche forma di file? Hanno un socket in ascolto? Fanno polling da qualche parte?
RaouL_BennetH
26-09-2012, 09:40
Io lo vedevo un lavoro perfetto per Workflow Foundation.
Non ho capito la tua ultima richiesta. Cosa intendi di preciso per "inviare i dati fra un terminale e l'altro"?
Questi terminali non leggono le informazioni da un db? Ricevono una qualche forma di file? Hanno un socket in ascolto? Fanno polling da qualche parte?
Ciao :)
Si, leggono dati solo da un db. Non c'è nessun servizio che gestisce l'applicazione.
Provo a spiegarmi meglio, ieri ero davvero stanchissimo :(
//i nomi di metodi ed entità sono solo concettuali
private void Foo(int prodottoID, int terminaleID, bool segnaleRicevuto)
{
int counter = context.ProdottoSchedaLavorazioni.Where(x => x.id_prodotto == prodottoID).Count();
//ora prendo la prima fase di lavorazione
var primoStep = context.FaseLavori.First(x => x.ProdottoSchedaLavorazioni.id_prodotto == prodottoID && x.id_terminale == terminaleID);
if(segnaleRicevuto) //segnale ricevuto dal terminale che ha la prima fase: vuol dire "ho iniziato a lavorare il prodotto"
{
for(int i = primoStep.progressivo; i <= counter; i++)
{
//prendo la fase successiva
var nextStep = context.FaseLavori.Single(x => x.progressivo == i);
//in ogni step c'è un tempo che indica la durata della lavorazione
//ad esempio 100
//devo utilizzare questo tempo per mettere in "pausa" la query che verrà visualizzata sul terminale di chi ha lo stepSuccessivo
//nel senso che se il primo step dura 100, il bind dei dati
//sul terminale di chi ha il secondo step deve apparire dopo 100
//il secondo step avrà anche lui una durata, ad es. 200
//quindi se c'è un terzo (o N) dovranno visualizzare i dati a partire
//dalla durata (tempo1 + tempo2 + tempoN).
}
}
}
Grazie mille per qualsiasi suggerimento !!
DarkSiDE
26-09-2012, 10:25
Ciao :)
Si, leggono dati solo da un db. Non c'è nessun servizio che gestisce l'applicazione.
Provo a spiegarmi meglio, ieri ero davvero stanchissimo :(
//i nomi di metodi ed entità sono solo concettuali
private void Foo(int prodottoID, int terminaleID, bool segnaleRicevuto)
{
int counter = context.ProdottoSchedaLavorazioni.Where(x => x.id_prodotto == prodottoID).Count();
//ora prendo la prima fase di lavorazione
var primoStep = context.FaseLavori.First(x => x.ProdottoSchedaLavorazioni.id_prodotto == prodottoID && x.id_terminale == terminaleID);
if(segnaleRicevuto) //segnale ricevuto dal terminale che ha la prima fase: vuol dire "ho iniziato a lavorare il prodotto"
{
for(int i = primoStep.progressivo; i <= counter; i++)
{
//prendo la fase successiva
var nextStep = context.FaseLavori.Single(x => x.progressivo == i);
//in ogni step c'è un tempo che indica la durata della lavorazione
//ad esempio 100
//devo utilizzare questo tempo per mettere in "pausa" la query che verrà visualizzata sul terminale di chi ha lo stepSuccessivo
//nel senso che se il primo step dura 100, il bind dei dati
//sul terminale di chi ha il secondo step deve apparire dopo 100
//il secondo step avrà anche lui una durata, ad es. 200
//quindi se c'è un terzo (o N) dovranno visualizzare i dati a partire
//dalla durata (tempo1 + tempo2 + tempoN).
}
}
}
Grazie mille per qualsiasi suggerimento !!
per quello che ho capito devi pensare di mettere un listener sui terminali o un timer che interroghi periodicamente il db, come suggerito dall'utente sopra :)
DarkSiDE
26-09-2012, 10:37
doppio
RaouL_BennetH
28-09-2012, 19:14
Ok, direi che ci sono quasi:
namespace JobHelper.ServiceManager
public static class JobsQueue
{
public static Queue<ObjJob> QListener = new Queue<ObjJob>();
public static void OnSignal(int productID, int termID, int sequence, int signal)
if(signal == 0)
{
ObjJob obj = new ObjJob(productID, termID, sequence);
QListener.Enqueue(obj);
}
else
QListener.Dequeue();
}
public class ObjJob
{
public int ProductID {get; set;}
public int TermID {get; set;}
public int Sequence {get; set;}
//blah costruttore che prende come argomenti productID, termID e sequence
}
//somewhere in the horror code...
public IQueryable JobSequence(int termID)
{
IQueryable result = null;
int timerTick = 10;
foreach(var job in JobsQueue.QListener)
{
var currentJob = objContext.Job.Single(x => x.productID == job.ProductID);
var jobLine = currentJob.JobLines.Single(x => x.termID == job.TermID
&& x.sequence == job.Sequence);
if(jobLine.sequence > 1)
{
timerTick = jobLine.TotalMinutes;
}
result = //blablbalba
Thread.Sleep(timerTick);
return result;
}
}
A video e con un pò di paziente debug, i risultati sono quelli che mi aspetto.
Ma.... (c'è sempre uno stramaledetto 'ma')....
ovviamente quando uso Thread.Sleep, l'applicazione (giustamente) è "ferma" e
l'utente non può interagire.
Cosa posso utilizzare per fare in modo che durante i vari "refresh" l'utente abbia la possibilità di interagire con l'applicazione ?
Mi viene in mente un backgroundworker, ma io mi trovo ancora lato "logica" e non UI.
Grazie
RaouL.
RaouL_BennetH
28-09-2012, 21:45
....
mi sono comunque accorto che così, non è che funzioni come dovrebbe...
in pratica fa un semplice refresh per un intervallo di tempo costante.... :muro: :mc: :cry:
tomminno
29-09-2012, 11:31
Non capisco il perchè di questo codice:
Thread.Sleep(timerTick);
return result;
E' dentro il foreach quindi in realtà esci al primo elemento di JobsQueue.QListener, ma principalmente non capisco perchè devi ritardare il ritorno del metodo.
Non potresti intanto leggere i dati metterli in un oggetto che solleva un evento di "DataReady" quando è passato timerTick?
Ovviamente questo prevede che tu abbia un software multithread in cui un thread principale parallelizza l'esecuzione del tuo foreach e riceve eventi dai thread avviati per poi segnalare ulterioriormente a chi di dovere il fatto che sono arrivati dati.
Questa struttura multi-thread probabilmente cozza con la natura statica di JobsQueue (forse ti serve una ConcurrentQueue).
Inoltre dovresti stare attento ai timeout. Mi sembra di capire che leggi qualcosa da db, ma se poi vai a riutilizzarlo magari un bel po' di tempo dopo potresti avere problemi con la connessione.
RaouL_BennetH
29-09-2012, 16:00
Non capisco il perchè di questo codice:
Thread.Sleep(timerTick);
return result;
E' dentro il foreach quindi in realtà esci al primo elemento di JobsQueue.QListener, ma principalmente non capisco perchè devi ritardare il ritorno del metodo.
Non potresti intanto leggere i dati metterli in un oggetto che solleva un evento di "DataReady" quando è passato timerTick?
Ovviamente questo prevede che tu abbia un software multithread in cui un thread principale parallelizza l'esecuzione del tuo foreach e riceve eventi dai thread avviati per poi segnalare ulterioriormente a chi di dovere il fatto che sono arrivati dati.
Questa struttura multi-thread probabilmente cozza con la natura statica di JobsQueue (forse ti serve una ConcurrentQueue).
Inoltre dovresti stare attento ai timeout. Mi sembra di capire che leggi qualcosa da db, ma se poi vai a riutilizzarlo magari un bel po' di tempo dopo potresti avere problemi con la connessione.
Wow... sono quindi ad un punto morto...
Allora, lo scenario è questo:
sul server gira soltanto il db (è sql server)
l'applicazione è su ogni pc, quindi, ogni unità ha il suo eseguibile.
Adesso... ovviamente l'applicazione è la stessa per ogni pc.
Quando l'applicazione parte, l'utente fa un login ed in base a dei parametri
viene riconosciuto come tipo di unità, ad esempio:
passa-filo
decoratore
etc...
c'è un'altra unità che invece gestisce gli ordini:
viene immesso l'ordine di un prodotto; il prodotto ha delle fasi di lavorazione;
ciascuna fase di lavorazione ha una sola competenza(passa-filo, decoratore, etc..) ed ha un ordine cronologico di lavorazione, ad es.:
1) passa-filo
2) decoratore
3) imballaggio
ciascuna lavorazione ha un tempo di lavorazione:
prima fase 2 minuti
seconda fase 3 minuti
terza fase 1 minuto
a livello di applicazione deve succedere questo:
il pc che corrisponde alla competenza della prima fase di lavorazione
deve ricevere a video che deve iniziare appunto la lavorazione
finito il tempo di lavorazione della prima fase, il pc che corrisponde
alla competenza successiva riceve a video la notifica che la fase uno è terminata e deve iniziare la sua lavorazione....
e così via fin quando non si arriva all'ultima fase (l'imballaggio)
Quando l'ultima fase è completata, l'oggetto può essere rimosso dalla coda e non essere più visualizzato sui vari terminali.
RaouL_BennetH
29-09-2012, 18:22
Ok, mi sto documentando sul namespace System.Collections.Concurrent.
Ho letto che in pratica il mio è il classico scenario "producer - consumer" e mi si suggerisce di usare una blockingcollection<T>.
Io sto procedendo così ma mi manca proprio la capacità di capire come redirigere i dati a tempo ed in sequenza:
public static BlockingCollection<MyObject> queue = new BlockingCollection<MyObject>();
public static void Test(MyObject obj)
{
this.obj = obj;
queue.Add(obj);
}
public void Test2()
{
foreach(var item in queue.GetConsumingEnumerable())
MessageBox.Show(item.BlahProperty...);
}
Vedo correttamente ciò che c'è in coda.
Plz... datemi una mano .... :help:
RaouL.
RaouL_BennetH
30-09-2012, 02:30
Ok... tralascio per il momento il mondo "parallelo". E' un mondo nuovo per me e mi richiede tempi di apprendimento che per ora non ho.
Butterei quindi qualsiasi considerazione fatta su code et similia e mi concentrerei banalmente su query classiche.
in pseudo codice quindi:
//per qualsiasi terminale (pc... o monitor che dir si voglia) la sorgente dati è
public IQueryable FillData(int computerID)
{
var result = from a in myContext.MyData.Where(x => x.computerID == computerID)
select new
{
//blabladati
};
return result;
}
//in questi dati, per ciascuna fase di lavoro ci sono l'inizio e la fine (quindi la durata) della lavorazione
//come faccio a passare da una fase all'altra allo scadere dei rispettivi tempi di lavorazione ?
:muro: :muro: :muro:
tomminno
01-10-2012, 13:53
Però scusa se hai istanze del software differenti che gestiscono le varie fasi cosa ti impedisce di impostare uno stato su db alla fine di una fase di lavorazione?
L'istanza del software che gestisce la fase X che ha durata T1 esegue e al termine del tempo T imposta lo stato su db.
Il software che gestisce la fase X+1 controlla lo stato su db e se è quello giusto fa partire la lavorazione, alla scadenza del tempo T2 imposta su db lo stato successivo e così via.
Questo stato può essere monitorato tramite polling su db(soluzione più semplice e immediata) dalle altre istanze (ti ci vorrà anche lo stato di "work in progress").
RaouL_BennetH
02-10-2012, 08:47
Però scusa se hai istanze del software differenti che gestiscono le varie fasi cosa ti impedisce di impostare uno stato su db alla fine di una fase di lavorazione?
L'istanza del software che gestisce la fase X che ha durata T1 esegue e al termine del tempo T imposta lo stato su db.
Il software che gestisce la fase X+1 controlla lo stato su db e se è quello giusto fa partire la lavorazione, alla scadenza del tempo T2 imposta su db lo stato successivo e così via.
Questo stato può essere monitorato tramite polling su db(soluzione più semplice e immediata) dalle altre istanze (ti ci vorrà anche lo stato di "work in progress").
Dovrei farlo solo lato codice, sul db non ho modo di intervenire.
Posso provare a simularlo su una replica in locale.
Grazie comunque per il suggerimento.
tomminno
02-10-2012, 15:37
Dovrei farlo solo lato codice, sul db non ho modo di intervenire.
Posso provare a simularlo su una replica in locale.
Grazie comunque per il suggerimento.
Un'altra alternativa è hostare un wcf dentro il software per comunicare la fine di una operazione agli altri software. Ovviamente il problema diventa di sapere dove stanno gli altri client a meno di non arrivare ad utilizzare una combinazione di Ws-Discovery e Wcf Routing.
Però non avresti bisogno dell'appoggio del db, se proprio non hai modo di fartelo modificare.
RaouL_BennetH
02-10-2012, 23:32
Mmm... mi sento sempre più confuso...
al momento sto simulando l'avanzamento delle sequenze in modo manuale, nel senso che quando un terminale indica che ha finito la sua lavorazione, la query viene visualizzata sul terminale successivo:
public IQueryable QueueSequence(int termID)
{
IQueryable coda = null;
var items = context.VQueue.Where(x => x.termID == termID);
foreach (var item in items)
{
if (item.jobSequence < 2 && item.progressivo == 1)
{
coda = from jobs in context.VQueue.Where(x => x.jobSequence < 2 && x.progressivo == 1)
select new
{
idr = jobs.id_productOrder,
hSend = jobs.hSend,
linea = jobs.lineaoloDesc,
product = jobs.product,
id_product = jobs.productID,
pritem = jobs.progressivo,
sequence = jobs.jobSequence,
term = jobs.termID
};
}
if (item.jobSequence >= 2 && item.jobSequence < 4 && item.progressivo == 2 && !item.isLast)
{
coda = from jobs in context.VQueue.Where(x => x.jobSequence >= 2 && x.jobSequence < 4 && x.progressivo == 2 && !item.isLast)
select new
{
idr = jobs.id_productOrder,
hSend = jobs.hSend,
linea = jobs.lineaoloDesc,
product = jobs.producticoloDesc,
id_product = jobs.producticoloPfID,
pritem = jobs.progressivo,
sequence = jobs.jobSequence,
term = jobs.termID
};
}
if (item.jobSequence >= 4 && item.progressivo == 3 && item.isLast)
{
coda = from jobs in context.VQueue.Where(x => x.jobSequence >= 4 && x.progressivo == 3 && x.isLast)
select new
{
idr = jobs.id_productOrder,
hSend = jobs.hSend,
linea = jobs.lineaoloDesc,
product = jobs.producticoloDesc,
id_product = jobs.producticoloPfID,
pritem = jobs.progressivo,
sequence = jobs.jobSequence,
term = jobs.termID
};
}
}
return coda;
}
Mi rendo conto dell'orrore, ma sono giorni che sto cercando di venirne a capo e anzichè andare avanti sto regredendo...
ad ogni modo:
nel caso di sopra, simulo un prodotto che ha tre sequenze di lavoro e delle quali la terza è quella di chiusura;
vi chiedo:
posso fare in modo che si comporti allo stesso modo a prescindere da quante sequenze ci sono ? Intendo che al momento è tale la confusione che non riesco neanche a pensare a come ciclare all'interno..
Grazie sempre per il supporto.
RaouL.
Kralizek
03-10-2012, 07:54
non so molto del tuo problema, ma se hai un problema di gestione dello stato, potresti pensare di aiutarti con Workflow Foundation. Ha una curva di apprendimento un po' troppo ripida all'inizio ma potrebbe valerne la pena :)
banryu79
03-10-2012, 08:17
Ciao Raoul, riprendendo il tuo esempio:
id=21; fkID=15; startJob = 1; jobLenght = 100; isLast = 0; workerID = 10;
id=22; fkID=15; startJob = 101; jobLenght = 100; isLast = 0; workerID = 19;
id=23; fkID=15; startJob = 201; jobLenght = 0; isLast = 1; workerID = 61;
puoi chiarire una cosa? Quando il worker10 ha terminato la sua lavorazione (il suo timer è scaduto) cosa deve fare? Deve aspettare che anche le fasi seguenti (worker19 e worker61) siano terminate per procedere con le sue eventuali uteriori lavorazioni degli ordini seguenti (i worker che lavorano un prodotto si aspettano) oppure può proseguire (i worker sono completamente indipendenti tra loro, a parte ovviamente la faccenda della sequenza delle lavorazioni)?
EDIT:
Inoltre: i vari terminali (i vari worker, se ho capito bene) possono comunicare tra loro (LAN)?
Ho capito che non puoi toccare il db, ma i worker ci possono accedere, questo mi porta a chiederti: volendo puoi installare un applicazione sul pc dove c'è il db per comunicare con i worker?
RaouL_BennetH
03-10-2012, 11:00
Ciao Raoul, riprendendo il tuo esempio:
puoi chiarire una cosa? Quando il worker10 ha terminato la sua lavorazione (il suo timer è scaduto) cosa deve fare? Deve aspettare che anche le fasi seguenti (worker19 e worker61) siano terminate per procedere con le sue eventuali uteriori lavorazioni degli ordini seguenti (i worker che lavorano un prodotto si aspettano) oppure può proseguire (i worker sono completamente indipendenti tra loro, a parte ovviamente la faccenda della sequenza delle lavorazioni)?
Ciao :)
Rispondendo alla prima domanda:
Il worker che ha finito la sua lavorazione può proseguire.
EDIT:
Inoltre: i vari terminali (i vari worker, se ho capito bene) possono comunicare tra loro (LAN)?
Ho capito che non puoi toccare il db, ma i worker ci possono accedere, questo mi porta a chiederti: volendo puoi installare un applicazione sul pc dove c'è il db per comunicare con i worker?
L'applicazione può funzionare in due modi:
1)gli eseguibili risiedono sul server e vengono eseguiti dai clients.
2)Ogni client ha il suo eseguibile in locale e tutti i clients ovviamente hanno in comune soltanto il db.
Si, sui clients posso installare qualunque cosa.
RaouL_BennetH
03-10-2012, 11:12
non so molto del tuo problema, ma se hai un problema di gestione dello stato, potresti pensare di aiutarti con Workflow Foundation. Ha una curva di apprendimento un po' troppo ripida all'inizio ma potrebbe valerne la pena :)
Leggendone la documentazione mi rendo conto che è esattamente ciò che serve per risolvere . Ma al momento i miei problemi derivano anche dal fatto che lavoro su codice già esistente e l'applicazione è già in produzione.
RaouL_BennetH
07-10-2012, 00:14
a distanza di qualche giorno mi sento davvero frustrato....
Devo scartare l'ipotesi delle Queue (di qualsiasi natura siano) perchè alla fine l'applicazione che gira sui clients, ha in comune con il server soltanto il db.
Non ho modo di mettere mano lato db (e probabilmente neanche ne sarei capace) e quindi... mi resta soltanto linq to sql e linq to object...
Riesco ad eseguire il compito se ho un numero prefissato (ad esempio 3) di steps per prodotto, nel senso che ho associato al termine di ciascuno step un evento.
Nella realtà però, io non so ne quante unità di lavoro ci saranno ne quanti steps di lavorazione può avere un prodotto..
:help:
banryu79
08-10-2012, 11:55
Scusa, ma non puoi fare due applicazioni?
Una è il client che già hai, installato/usato dai vari pc worker, l'altra sarebbe un server che gestisce una coda.
I client/worker pubblicano sulla coda [spediscono al server] gli eventi (evento = ID lavoro, ID fase conclusa, ID prossimo worker) e il server li scoda e li processa (notifica i prossimi worker che devono procedere, aggiorna il DB)
In pratica l'idea è che l'applicazione server è il punto di contatto tra i vari worker. Inoltre il server è l'unico ad accedere al db.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.