【问题标题】:Problem synchronizing with ManualResetEvent与 ManualResetEvent 同步时出现问题
【发布时间】:2010-07-20 08:45:11
【问题描述】:

我已经编写了一个下载一些文件的方法,现在我正试图让它并行下载多达 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 for Windows Phone 7 编写的。

编辑:Silverlight 4 中没有 Semaphore 或 SemaphoreSlim。

【问题讨论】:

  • 可以使用System.Threading.Interlocked.Decrement()等方法时为什么要锁定?
  • 因为我也想调用sync.Set(),而且我认为有人从另一个线程冷调用sync.Set(),然后我递减,一些线程递增并调用sync.Reset(),然后我调用 sync.Set() 并获得更多的 maxFiles 线程下载。
  • 检查我的答案,这就是你要找的。此外,使用重置事件是可以的,我只是不认为需要锁。哦,我使用 AutoResetEvent,因为它正是您需要的。

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


【解决方案1】:

我在评论中的意思是当你可以使用Interlocked 时使用慢速lock。这种方式的性能也会更高。

最多有 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);
 }
}

【讨论】:

  • 该类应该是静态的(代码也适用于静态同步)。
  • 是的,这种方式性能更高......但我仍然有同样的问题......也许它不是来自 AutoResetEvent / ManualResetEvent。
  • @user182945:你试过我的代码吗?它永远无法达到 5+ 并发下载。如果您的回调未执行,则问题可能出在其他地方。也许它在您的下载逻辑中。我把它空了用于测试,它运行良好。
【解决方案2】:

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

【讨论】:

  • Silverlight for Windows Phone 中似乎没有信号量。还有什么类似于我可以使用的信号量的东西吗?
  • 啊-是的-抱歉-也许不是。我正在查看完整框架的文档。我在这里找到了一些有用的链接,人们在这里讨论 Silverlight 中的替代方案:stackoverflow.com/questions/2307844/…forums.silverlight.net/forums/p/20199/69433.aspx
  • Rob 是正确的;您使用了错误的同步原语。如果您使用的是 .NET 4.0,请改用 System.Threading.SemaphoreSlim - 它轻巧、快速,并且支持取消令牌,在执行下载文件等长时间运行的操作时,您可能会发现该功能很有用。编辑:花了很长时间输入...
  • @Allon Guralnek 这在 Silverlight 中是否存在?
  • 通过 Reflector 查看 SemaphoreSlim 的反汇编,您也许可以将整个类复制粘贴到您的项目中 - 在内部它使用 ManualResetEvent:pastebin.com/cFmgihdB 排除很多东西的原因Silverlight 通常是为了保持 Silverlight 插件的下载大小较小,而不是技术原因。反射器是弥补这些缺失位的好方法(当它起作用时)。
【解决方案3】:

从外观上看,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;
            }

【讨论】:

  • 我想创建 WebClient 并仅在获得 ++ 文件后才调用 OpenReadAsync;对于当前的 DownloadAsync 调用。这样它只会一次下载 maxFiles,其余的则等待之前的 1 个下载完成。 (我在调试时没有得到那部分的 NullReferenceException,所以我猜它是正确创建的)。
猜你喜欢
  • 2018-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多