SpinWait vs Sleep esperando. ¿Cuál usar?

¿Es eficiente

SpinWait.SpinUntil(() => myPredicate(), 10000) 

por un tiempo de espera de 10000ms

o

¿Es más eficiente utilizar Thread.Sleep polling para la misma condición? Por ejemplo, algo SleepWait la siguiente función SleepWait :

 public bool SleepWait(int timeOut) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (!myPredicate() && stopwatch.ElapsedMilliseconds < timeOut) { Thread.Sleep(50) } return myPredicate() } 

Me preocupa que todo el rendimiento de SpinWait no sea un buen patrón de uso si hablamos de tiempos de espera superiores a 1 segundo. ¿Es esto una suposición válida?

¿Qué enfoque prefieres y por qué? ¿Hay otro enfoque aún mejor?


Actualización : volviéndose más específico:

¿Hay alguna manera de hacer que el BlockingCollection Pulse sea un hilo que duerme cuando alcanza una capacidad limitada? Prefiero evitar las agitadas esperas, como sugiere Marc Gravel.

El mejor enfoque es tener algún mecanismo para detectar activamente que la cosa se haga realidad (en lugar de sondear pasivamente porque se ha hecho realidad); esto podría ser cualquier tipo de espera, o tal vez una Task con Wait , o tal vez un event que puedas suscribirte para despegarte. Por supuesto, si haces ese tipo de “esperar hasta que pase algo”, eso no es tan eficiente como simplemente hacer el siguiente trabajo como callback , lo que significa que no necesitas usar un hilo para esperar. Task tiene ContinueWith para esto, o puede simplemente hacer el trabajo en un event cuando se dispara. El event es probablemente el enfoque más simple, según el contexto. Task , sin embargo, ya proporciona la mayor parte de todo lo que usted está hablando aquí, incluidos los mecanismos de “espera con tiempo de espera” y “callback”.

Y sí, girar durante 10 segundos no es genial. Si desea usar algo como su código actual, y si tiene motivos para esperar un pequeño retraso, pero necesita permitir uno más largo, ¿tal vez SpinWait para (digamos) 20 ms, y use Sleep para el rest?


Re el comentario; así es como engancharía un mecanismo “¿está lleno?”

 private readonly object syncLock = new object(); public bool WaitUntilFull(int timeout) { if(CollectionIsFull) return true; // I'm assuming we can call this safely lock(syncLock) { if(CollectionIsFull) return true; return Monitor.Wait(syncLock, timeout); } } 

con, en el código “poner de nuevo en la colección”:

 if(CollectionIsFull) { lock(syncLock) { if(CollectionIsFull) { // double-check with the lock Monitor.PulseAll(syncLock); } } } 

En .NET 4, SpinWait realiza un centrifugado intensivo de CPU durante 10 iteraciones antes de ceder. Pero no regresa a la persona que llama inmediatamente después de cada uno de esos ciclos; en su lugar, llama a Thread.SpinWait para girar a través del CLR (esencialmente el sistema operativo) durante un período de tiempo establecido. Este período de tiempo es inicialmente unas pocas decenas de nano segundos, pero se duplica con cada iteración hasta que se completen las 10 iteraciones. Esto permite claridad / previsibilidad en el tiempo total dedicado a la fase de giro (intensivo en CPU), que el sistema puede sintonizar de acuerdo con las condiciones (número de núcleos, etc.). Si SpinWait permanece en la fase de producción de spin por mucho tiempo, dormirá periódicamente para permitir que continúen los otros hilos (ver el blog de J. Albahari para más información). Este proceso está garantizado para mantener un núcleo ocupado …

Por lo tanto, SpinWait limita el giro intensivo de CPU a un número determinado de iteraciones, después de lo cual genera su intervalo de tiempo en cada vuelta (al llamar realmente a Thread.Yield y Thread.Sleep ), lo que reduce el consumo de recursos. También detectará si el usuario está ejecutando una única máquina central y cederá en cada ciclo si ese es el caso.

Con Thread.Sleep el hilo está bloqueado. Pero este proceso no será tan caro como el anterior en términos de CPU.