Проблема синхронизации с ManualResetEvent
Я написал метод, который загружает некоторые файлы, и теперь я пытаюсь сделать так, чтобы он загружал до 5 файлов параллельно, а остальные ждали завершения предыдущих. Я использую ManualResetEvent для этого, но когда я включаю часть синхронизации, она больше ничего не загружает (без нее это работает).
Вот код методов:
static readonly int maxFiles = 5;
static int files = 0;
static object filesLocker = new object();
static System.Threading.ManualResetEvent sync = new System.Threading.ManualResetEvent(true);
/// <summary>
/// Download a file from wikipedia asynchronously
/// </summary>
/// <param name="filename"></param>
public void DoanloadFileAsync(string filename)
{
...
System.Threading.ThreadPool.QueueUserWorkItem(
(o) =>
{
bool loop = true;
while (loop)
if (sync.WaitOne())
lock (filesLocker)
{
if (files < maxFiles)
{
++files;
if (files == maxFiles)
sync.Reset();
loop = false;
}
}
try
{
WebClient downloadClient = new WebClient();
downloadClient.OpenReadCompleted += new OpenReadCompletedEventHandler(downloadClient_OpenReadCompleted);
downloadClient.OpenReadAsync(new Uri(url, UriKind.Absolute));
//5 of them do get here
}
catch
{
lock (filesLocker)
{
--files;
sync.Set();
}
throw;
}
});
}
void downloadClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
try
{
//but none of the 5 get here
...Download logic... //works without the ManualResetEvent
}
finally
{
lock (filesLocker)
{
--files;
sync.Set();
}
}
}
Я делаю что-то не так?
Он написан с Silverlight 4 для Windows Phone 7.
Редактировать: нет семафора или SemaphoreSlim в Silverlight 4.
3 ответа:
То, что я имел в виду в своем комментарии, было в то время как использовать медленный
lock
, Когда вы можете использоватьInterlocked
. Кроме того, это будет гораздо более эффективным способом.Не более 5 загрузок, активных параллельно:
public class Downloader { private int fileCount = 0; private AutoResetEvent sync = new AutoResetEvent(false); private void StartNewDownload(object o) { if (Interlocked.Increment(ref this.fileCount) > 5) this.sync.WaitOne(); WebClient downloadClient = new WebClient(); downloadClient.OpenReadCompleted += downloadClient_OpenReadCompleted; downloadClient.OpenReadAsync(new Uri(o.ToString(), UriKind.Absolute)); } private void downloadClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) { try { // Download logic goes here. } catch {} finally { this.sync.Set(); Interlocked.Decrement(ref this.fileCount); } } public void Run() { string o = "url1"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); Thread.Sleep(100); o = "url2"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); o = "url3"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); Thread.Sleep(200); o = "url4"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); o = "url5"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); o = "url6"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); o = "url7"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); Thread.Sleep(200); o = "url8"; System.Threading.ThreadPool.QueueUserWorkItem(this.StartNewDownload, o); Thread.Sleep(400); } }
Похоже, что вы пытаетесь ограничить количество потоков, которые могут войти в ваш критический раздел, загрузку файла, сразу. Вместо того, чтобы пытаться сделать это вручную, используйте
System.Threading.Semaphore
- вот что он делает!
По внешнему виду
WaitOne()
попадает до того, как вы создаетеWebClient
. Так как весь код, который вызываетSet()
, находится внутри обработчика событий или обработчика исключений, он никогда не попадет.Возможно, вы по ошибке включили код
WebClient
в метод потока пула потоков, который должен быть вне егоSystem.Threading.ThreadPool.QueueUserWorkItem( (o) => { bool loop = true; while (loop) if (sync.WaitOne()) lock (filesLocker) { if (files < maxFiles) { ++files; if (files == maxFiles) sync.Reset(); loop = false; } } }); //Have the try catch OUTSIDE the background thread. try { WebClient downloadClient = new WebClient(); downloadClient.OpenReadCompleted += new OpenReadCompletedEventHandler(downloadClient_OpenReadCompleted); downloadClient.OpenReadAsync(new Uri(url, UriKind.Absolute)); //5 of them do get here } catch { lock (filesLocker) { --files; sync.Set(); } throw; }