questão C # Enfiar com AutoResetEvent
-
18-09-2019 - |
Pergunta
Como sincronizar corretamente este? No momento, é possível que SetData
é chamado após e.WaitOne()
completou tão d
já poderia ser definido para outro valor. Tentei inserir fechaduras mas resultou em um impasse.
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);
}
}
Será que a melhor solução seria introduzir uma nova dataAlreadyBeenSetAndWaitingToBeProcessed
variável booleana que está definido no SetData
como true e no final do DoLongOperationWith_d
que poderia ser definido como verdadeiro, então se SetData
é chamado com este conjunto variável a verdade poderia apenas retorno ?
Solução
Esta é testado, mas é uma maneira elegante de fazer isso com os primitivos baseado em .NET:
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);
}
}
}
Exemplo de programa:
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);
}
}
Esta é uma versão de não-fila, uma fila de versão podem ser preferidos:
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;
}
}
}
Outras dicas
Há dois cenários possivelmente preocupantes aqui.
1:
- DoLongOperationWith_d (d) acabamentos.
- SetData () é chamado, armazenar um novo valor no d.
- e.WaitOne () é chamado, mas desde um valor já foi definido o thread espera para sempre.
Se essa é a sua preocupação, eu acho que você pode relaxar. Do documentação , vemos que
Se um thread chama WaitOne enquanto o AutoResetEvent está no estado sinalizado, o segmento não bloqueia. O AutoResetEvent libera o fio imediatamente e retorna ao estado não sinalizado.
Então, isso não é um problema. No entanto, dependendo de como e quando SetData () é chamado, você pode estar lidando com a mais grave
2:
- SetData () é chamado, armazenar um novo valor no d e acordar o corredor.
- DoLongOperationWith_d (d) começa.
- SetData () é chamado novamente, armazenar um novo valor no d.
- SetData () é chamado novamente! O valor antigo da d está perdido para sempre; DoLongOperationWith_d () nunca será invocado sobre ela.
Se esse é o seu problema, a maneira mais simples de resolver isso é uma fila simultânea. Implementações abundam.
Você pode usar 2 eventos,
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();
}
}
Uma das coisas que você pode fazer com esta abordagem é ter SetData tomar um tempo limite, e passar isso para WaitOne
. Acho, contudo, você shoudl investigar ThreadPool.QueueUserWorkItem
.