Sono uno sviluppatore junior che lavora alla stesura di un aggiornamento per software che riceve dati da una soluzione di terze parti, li archivia in un database e quindi li condiziona per l'utilizzo da parte di un'altra soluzione di terze parti. Il nostro software funziona come un servizio Windows.
Guardando il codice di una versione precedente, vedo questo:
static Object _workerLocker = new object();
static int _runningWorkers = 0;
int MaxSimultaneousThreads = 5;
foreach(int SomeObject in ListOfObjects)
{
lock (_workerLocker)
{
while (_runningWorkers >= MaxSimultaneousThreads)
{
Monitor.Wait(_workerLocker);
}
}
// check to see if the service has been stopped. If yes, then exit
if (this.IsRunning() == false)
{
break;
}
lock (_workerLocker)
{
_runningWorkers++;
}
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
}
La logica sembra chiara: attendere lo spazio nel pool di thread, assicurarsi che il servizio non sia stato arrestato, quindi incrementare il contatore dei thread e mettere in coda il lavoro. _runningWorkersviene decrementato all'interno SomeMethod()all'interno di una lockdichiarazione che le chiamate poi Monitor.Pulse(_workerLocker).
La mia domanda è:
c'è qualche vantaggio nel raggruppare tutto il codice in un singolo lock, in questo modo:
static Object _workerLocker = new object();
static int _runningWorkers = 0;
int MaxSimultaneousThreads = 5;
foreach (int SomeObject in ListOfObjects)
{
// Is doing all the work inside a single lock better?
lock (_workerLocker)
{
// wait for room in ThreadPool
while (_runningWorkers >= MaxSimultaneousThreads)
{
Monitor.Wait(_workerLocker);
}
// check to see if the service has been stopped.
if (this.IsRunning())
{
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
_runningWorkers++;
}
else
{
break;
}
}
}
Sembra che potrebbe causare un po 'più di attesa per altri thread, ma poi sembra che il blocco ripetuto in un singolo blocco logico richiederebbe un po' di tempo. Tuttavia, sono nuovo del multi-threading, quindi presumo che ci siano altre preoccupazioni di cui non sono a conoscenza.
Gli unici altri posti in cui _workerLockerviene bloccato sono dentro SomeMethod(), e solo allo scopo di decrementare _runningWorkers, e quindi fuori foreachdall'attesa che il numero di _runningWorkersvada a zero prima di accedere e tornare.
Grazie per qualsiasi aiuto.
MODIFICA 4/8/15
Grazie a @delnan per la raccomandazione di usare un semaforo. Il codice diventa:
static int MaxSimultaneousThreads = 5;
static Semaphore WorkerSem = new Semaphore(MaxSimultaneousThreads, MaxSimultaneousThreads);
foreach (int SomeObject in ListOfObjects)
{
// wait for an available thread
WorkerSem.WaitOne();
// check if the service has stopped
if (this.IsRunning())
{
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
}
else
{
break;
}
}
WorkerSem.Release()si chiama dentro SomeMethod().
SomeMethodmodo asincrono, la sezione "blocco" sopra verrà lasciata prima o almeno poco dopo l' SomeMethodinizio del nuovo thread con inizio.
Monitor.Wait()è quello di rilasciare e riacquisire il blocco in modo che un'altra risorsa ( SomeMethod, in questo caso) possa utilizzarlo. Dall'altro lato, SomeMethodottiene il blocco, decrementa il contatore e quindi chiama Monitor.Pulse()che restituisce il blocco al metodo in questione. Ancora una volta, questa è la mia comprensione.
Monitor.Waitrilascia il lucchetto. Consiglio di dare un'occhiata ai documenti.