problema C # Threading con AutoResetEvent
-
18-09-2019 - |
Domanda
Come sincronizzare correttamente questo? Al momento è possibile che SetData
viene richiamato dopo e.WaitOne()
ha completato così d
potrebbe essere già impostato un altro valore. Ho cercato di inserire serrature ma il risultato in una situazione di stallo.
AutoResetEvent e = new AutoResetEvent(false);
public SetData(MyData d)
{
this.d=d;
e.Set(); // notify that new data is available
}
// This runs in separate thread and waits for d to be set to a new value
void Runner()
{
while (true)
{
e.WaitOne(); // waits for new data to process
DoLongOperationWith_d(d);
}
}
Sarà la soluzione migliore quella di introdurre un nuovo dataAlreadyBeenSetAndWaitingToBeProcessed
variabile booleana che è impostata in SetData
true e alla fine di DoLongOperationWith_d
potrebbe essere impostata su true, quindi se SetData
viene chiamato con questo set di variabili al vero potrebbe essere solo il ritorno ?
Soluzione
Questa non è testato, ma è un modo elegante per fare questo con le primitive .net base:
class Processor<T> {
Action<T> action;
Queue<T> queue = new Queue<T>();
public Processor(Action<T> action) {
this.action = action;
new Thread(new ThreadStart(ThreadProc)).Start();
}
public void Queue(T data) {
lock (queue) {
queue.Enqueue(data);
Monitor.Pulse(queue);
}
}
void ThreadProc() {
Monitor.Enter(queue);
Queue<T> copy;
while (true) {
if (queue.Count == 0) {
Monitor.Wait(queue);
}
copy = new Queue<T>(queue);
queue.Clear();
Monitor.Exit(queue);
foreach (var item in copy) {
action(item);
}
Monitor.Enter(queue);
}
}
}
Esempio di programma:
class Program {
static void Main(string[] args) {
Processor<int> p = new Processor<int>((data) => { Console.WriteLine(data); });
p.Queue(1);
p.Queue(2);
Console.Read();
p.Queue(3);
}
}
Questa è una versione non in coda, una versione della coda può essere preferito:
object sync = new object();
AutoResetEvent e = new AutoResetEvent(false);
bool pending = false;
public SetData(MyData d)
{
lock(sync)
{
if (pending) throw(new CanNotSetDataException());
this.d=d;
pending = true;
}
e.Set(); // notify that new data is available
}
void Runner() // this runs in separate thread and waits for d to be set to a new value
{
while (true)
{
e.WaitOne(); // waits for new data to process
DoLongOperationWith_d(d);
lock(sync)
{
pending = false;
}
}
}
Altri suggerimenti
Ci sono due scenari preoccupanti forse qui.
1
- DoLongOperationWith_d (d) finisce.
- SetData () viene chiamato, la memorizzazione di un nuovo valore di d.
- e.WaitOne () viene chiamato, ma dal momento che un valore è già stato impostato il thread attende per sempre.
Se questo è la vostra preoccupazione, penso che ci si può rilassare. Dal , vediamo che
Se un thread chiama WaitOne mentre l'AutoResetEvent è nello stato segnalato, il filo non bloccare. L'AutoResetEvent rilascia il filo immediatamente e ritorna allo stato non segnalato.
In modo che non è un problema. Tuttavia, a seconda di come e quando si chiama SetData (), si può avere a che fare con la più grave
2
- SetData () viene chiamato, la memorizzazione di un nuovo valore di d e svegliare il corridore.
- DoLongOperationWith_d (d) si avvia.
- SetData () viene chiamato di nuovo, la memorizzazione di un nuovo valore di d.
- SetData () viene chiamato di nuovo! Il vecchio valore di d è perso per sempre; DoLongOperationWith_d () non potrà mai essere invocato su di esso.
Se questo è il vostro problema, il modo più semplice per risolverlo è una coda concomitante. Implementazioni abbondano.
È possibile utilizzare 2 eventi,
AutoResetEvent e = new AutoResetEvent(false);
AutoResetEvent readyForMore = new AutoResetEvent(true); // Initially signaled
public SetData(MyData d)
{
// This will immediately determine if readyForMore is set or not.
if( readyForMore.WaitOne(0,true) ) {
this.d=d;
e.Set(); // notify that new data is available
}
// you could return a bool or something to indicate it bailed.
}
void Runner() // this runs in separate thread and waits for d to be set to a new value
{
while (true)
{
e.WaitOne(); // waits for new data to process
DoLongOperationWith_d(d);
readyForMore.Set();
}
}
Una delle cose che si possono fare con questo approccio è avere SetData prendere un timeout, e passare che in WaitOne
. Credo che comunque si shoudl indagare ThreadPool.QueueUserWorkItem
.