与ManualResetEvent同步的问题

时间:2010-07-20 08:45:11

标签: c# synchronization silverlight-4.0 windows-phone-7

我编写了一个下载某些文件的方法,现在我试图让它最多并行下载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();
            }
        }
    }

我做错了吗?

使用适用于Windows Phone 7的Silverlight 4编写。

编辑: Silverlight 4中没有信号量或SemaphoreSlim。

3 个答案:

答案 0 :(得分: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);
 }
}

答案 1 :(得分:1)

您似乎正在尝试一次性限制可以进入关键部分的线程数量,即文件下载。不要试图手工制作,而是使用System.Threading.Semaphore - 这就是它的作用!

答案 2 :(得分: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;
            }