Je développe une application console.
Je souhaite utiliser un Threadpool pour effectuer des téléchargements Web. Voici du faux code.
for (int loop=0; loop< 100; loop++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(GetPage), pageList[loop]);
}
snip
private static void GetPage(object o)
{
//get the page
}
Comment empêcher mon code de démarrer plus de deux (ou dix, ou autre) threads simultanés?
J'ai essayé
ThreadPool.SetMaxThreads(1, 0);
ThreadPool.SetMinThreads(1, 0);
Mais ils semblent n'avoir aucun impact.
J'utiliserais Parallel.For
et définissez MaxDegreeOfParallelism
en conséquence.
Parallel.For(0, 1000, new ParallelOptions { MaxDegreeOfParallelism = 10 },
i =>
{
GetPage(pageList[i]);
});
Inversez simplement ce code de:
ThreadPool.SetMaxThreads(1, 0);
ThreadPool.SetMinThreads(1, 0);
À:
ThreadPool.SetMinThreads(1, 0);
ThreadPool.SetMaxThreads(1, 0);
Vous ne pouvez pas définir le MaxThread plus petit que MinThread
Personnellement, j'utiliserais SmartThreadPool et laisserais le ThreadPool tranquille. Cependant, c'est probablement ce que vous voulez: threads limitant le pool de threads C #
Code inclus depuis le lien (merci de donner à l'auteur d'origine, pas moi)
System.Threading.Semaphore S = new System.Threading.Semaphore(3, 3);
try
{
// wait your turn (decrement)
S.WaitOne();
// do your thing
}
finally
{
// release so others can go (increment)
S.Release();
}
Vous pouvez le faire en utilisant le ThreadPool.SetMaxThreads
méthode.
Mais il existe certains problèmes lors de l'utilisation de ThreadPool pour WebRequest. Lisez, par exemple, this (Bug dans ThreadPool ou HttpWebRequest?)
ThreadPool.SetMaxThreads(2,2);
Personnellement, j'utiliserais AsParallel de Linq
, pour cela.
Regardez les paramètres de ThreadPool.SetMaxThreads. Le premier paramètre est la quantité de threads de travail et le second paramètre est la quantité de threads asynchrones, dont vous parlez.
Plus bas dans la documentation, il est écrit:
Vous ne pouvez pas définir le nombre de threads de travail ou le nombre de threads d’achèvement des E/S sur un nombre inférieur au nombre de processeurs de l’ordinateur.
Il semble que vous essayez d'utiliser le ThreadPool pour quelque chose pour lequel il n'est pas destiné à être utilisé. Si vous souhaitez limiter le nombre de téléchargements, créez une classe qui gère cela pour vous, car le ThreadPool n'est pas nécessairement la solution complète à votre problème.
Je suggère une classe qui démarre deux threads dans le ThreadPool et attend le rappel. Lorsqu'il reçoit un rappel pour l'achèvement de l'un des threads, en file d'attente un nouveau.
Si vous serrez à .Net 2.0, vous pouvez utiliser la technique suivante:
sachant que si vous mettez en file d'attente une tâche dans le ThreadPool
cela créera un nouveau thread (bien sûr s'il n'y en a pas de libre), vous attendrez avant de faire cela jusqu'à ce qu'il y ait un thread libre. À cet effet, la classe BlockingCounter
est utilisée (décrite ci-dessous) qui, une fois la limite atteinte, attendra pour s'incrémenter jusqu'à ce que quelqu'un (un autre thread) la décrémente. Il est ensuite entré dans l'état "fermé" indiquant qu'aucun nouvel incrément ne sera effectué et attend la fin.
Voici l'exemple qui montre un maximum de 4 tâches avec un nombre total de 10.
class Program
{
static int s_numCurrentThreads = 0;
static Random s_rnd = new Random();
static void Main(string[] args)
{
int maxParallelTasks = 4;
int totalTasks = 10;
using (BlockingCounter blockingCounter = new BlockingCounter(maxParallelTasks))
{
for (int i = 1; i <= totalTasks; i++)
{
Console.WriteLine("Submitting task {0}", i);
blockingCounter.WaitableIncrement();
if (!ThreadPool.QueueUserWorkItem((obj) =>
{
try
{
ThreadProc(obj);
}
catch (Exception ex)
{
Console.Error.WriteLine("Task {0} failed: {1}", obj, ex.Message);
}
finally
{
// Exceptions are possible here too,
// but proper error handling is not the goal of this sample
blockingCounter.WaitableDecrement();
}
}, i))
{
blockingCounter.WaitableDecrement();
Console.Error.WriteLine("Failed to submit task {0} for execution.", i);
}
}
Console.WriteLine("Waiting for copmletion...");
blockingCounter.CloseAndWait(30000);
}
Console.WriteLine("Work done!");
Console.ReadKey();
}
static void ThreadProc (object obj)
{
int taskNumber = (int) obj;
int numThreads = Interlocked.Increment(ref s_numCurrentThreads);
Console.WriteLine("Task {0} started. Total: {1}", taskNumber, numThreads);
int sleepTime = s_rnd.Next(0, 5);
Thread.Sleep(sleepTime * 1000);
Console.WriteLine("Task {0} finished.", taskNumber);
Interlocked.Decrement(ref s_numCurrentThreads);
}
Il utilise la classe BlockingCounter qui est basée sur SizeQueue de Marc Gravell publiée ici , mais sans compteur au lieu d'une file d'attente. Lorsque vous terminez la mise en file d'attente de nouveaux threads, appelez la méthode Close (), puis attendez qu'elle se termine.
public class BlockingCounter : IDisposable
{
private int m_Count;
private object m_counterLock = new object();
private bool m_isClosed = false;
private volatile bool m_isDisposed = false;
private int m_MaxSize = 0;
private ManualResetEvent m_Finished = new ManualResetEvent(false);
public BlockingCounter(int maxSize = 0)
{
if (maxSize < 0)
throw new ArgumentOutOfRangeException("maxSize");
m_MaxSize = maxSize;
}
public void WaitableIncrement(int timeoutMs = Timeout.Infinite)
{
lock (m_counterLock)
{
while (m_MaxSize > 0 && m_Count >= m_MaxSize)
{
CheckClosedOrDisposed();
if (!Monitor.Wait(m_counterLock, timeoutMs))
throw new TimeoutException("Failed to wait for counter to decrement.");
}
CheckClosedOrDisposed();
m_Count++;
if (m_Count == 1)
{
Monitor.PulseAll(m_counterLock);
}
}
}
public void WaitableDecrement(int timeoutMs = Timeout.Infinite)
{
lock (m_counterLock)
{
try
{
while (m_Count == 0)
{
CheckClosedOrDisposed();
if (!Monitor.Wait(m_counterLock, timeoutMs))
throw new TimeoutException("Failed to wait for counter to increment.");
}
CheckDisposed();
m_Count--;
if (m_MaxSize == 0 || m_Count == m_MaxSize - 1)
Monitor.PulseAll(m_counterLock);
}
finally
{
if (m_isClosed && m_Count == 0)
m_Finished.Set();
}
}
}
void CheckClosedOrDisposed()
{
if (m_isClosed)
throw new Exception("The counter is closed");
CheckDisposed();
}
void CheckDisposed()
{
if (m_isDisposed)
throw new ObjectDisposedException("The counter has been disposed.");
}
public void Close()
{
lock (m_counterLock)
{
CheckDisposed();
m_isClosed = true;
Monitor.PulseAll(m_counterLock);
}
}
public bool WaitForFinish(int timeoutMs = Timeout.Infinite)
{
CheckDisposed();
lock (m_counterLock)
{
if (m_Count == 0)
return true;
}
return m_Finished.WaitOne(timeoutMs);
}
public void CloseAndWait (int timeoutMs = Timeout.Infinite)
{
Close();
WaitForFinish(timeoutMs);
}
public void Dispose()
{
if (!m_isDisposed)
{
m_isDisposed = true;
lock (m_counterLock)
{
// Wake up all waiting threads, so that they know the object
// is disposed and there's nothing to wait anymore
Monitor.PulseAll(m_counterLock);
}
m_Finished.Close();
}
}
}
Le résultat sera comme ça:
Submitting task 1
Submitting task 2
Submitting task 3
Submitting task 4
Submitting task 5
Task 1 started. Total: 1
Task 1 finished.
Task 3 started. Total: 1
Submitting task 6
Task 2 started. Total: 2
Task 3 finished.
Task 6 started. Total: 4
Task 5 started. Total: 3
Task 4 started. Total: 4
Submitting task 7
Task 4 finished.
Submitting task 8
Task 7 started. Total: 4
Task 5 finished.
Submitting task 9
Task 7 finished.
Task 8 started. Total: 4
Task 9 started. Total: 4
Submitting task 10
Task 2 finished.
Waiting for copmletion...
Task 10 started. Total: 4
Task 10 finished.
Task 6 finished.
Task 8 finished.
Task 9 finished.
Work done!