2010-07-20 3 views
3

Я написал метод, который загружает некоторые файлы, и теперь я пытаюсь загрузить его до 5 файлов параллельно, а остальным ждать, пока предыдущие будут завершены. Я использую ManualResetEvent для этого, но когда я включаю часть syncronisation, он больше ничего не скачивает (без этого он работает).Проблема синхронизации с 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.

Edit: Там нет Семафор или SemaphoreSlim в Silverlight 4.

+0

Почему замок, когда вы можете использовать System.Threading.Interlocked.Decrement() '' метод, и т.д.? –

+0

Потому что я также хочу вызвать sync.Set(), и я думаю, что кто-то холодный вызов sync.Set() из другого потока, затем я уменьшаю, некоторые потоки увеличивают и вызывают sync.Reset(), затем я вызываю sync.Set (), и я получаю больше того, что загружает потоки maxFiles. – user182945

+1

Проверьте мой ответ, это то, что вы ищете. Кроме того, использование события сброса в порядке, я просто не вижу необходимости в блокировках. О, и я использую AutoResetEvent, потому что это именно то, что вам нужно здесь. –

ответ

4

Что я имел в виду в своем комментарии, было использование медленного 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); 
} 
} 
+0

Класс должен быть статичным (код также работает для статической синхронизации). –

+0

Да, это более реалистично ... но у меня все еще такая же проблема ... может быть, это не из AutoResetEvent/ManualResetEvent. – user182945

+0

@ user182945: Вы даже пробовали мой код? Он никогда не сможет достичь 5+ одновременных загрузок. Если ваши обратные вызовы не выполняются, возможно, проблема в другом месте. Возможно, это что-то в вашей логике загрузки. У меня он пустой для тестирования, и он работает хорошо. –

0

По выглядит 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; 
      } 
+0

Я хочу создать WebClient и вызвать OpenReadAsync только после того, как я получу файлы ++; для текущего вызова DownloadAsync. Так что он будет загружать только maxFiles сразу, а для остальных ждать завершения 1 из предыдущих загрузок. (Я не получаю исключение NullReferenceException для этой части, когда я отлаживаю, поэтому, думаю, он создан правильно). – user182945

1

кажется, что вы пытаетесь ограничить количество потоков, которые могут войти в свой критический раздел, файл скачать, сразу. Вместо того, чтобы пытаться использовать это оружие, используйте System.Threading.Semaphore - вот что он делает!

+0

В Silverlight для Windows Phone, похоже, нет семафора. Есть ли что-то еще похожее на семафор, который я могу использовать? – user182945

+0

ahh - да - извините - может быть, нет. Я смотрел документацию для полной структуры. Я нашел несколько полезных ссылок здесь, где люди обсуждают альтернативы в Silverlight: http://stackoverflow.com/questions/2307844/silverlight-dealing-with-async-calls и http://forums.silverlight.net/forums/ p/20199/69433.aspx –

+0

Роб прав; вы используете неправильный примитив синхронизации. Если вы используете .NET 4.0, вместо этого используйте 'System.Threading.SemaphoreSlim' - он легкий, быстрый и поддерживает токены отмены, что может оказаться полезным при выполнении длительных операций, таких как загрузка файлов. EDIT: долго набирал текст ... –

Смежные вопросы