2

I understand this question may be too general. But I tried many things and I am not able to figure out how to resolve this.

I am using ConcurrentQueue for multithreading operation. One thread is downloading Images from server and saving it to queue. Here is the code for that:

 public static void DownloadImage()
    {
        string baseUrl = "http://someurl";
        //int numIterations = 5;

        HttpWebRequest request = null;
        foreach (var fileName in fileNames)
        {
                string url = string.Format(baseUrl, fileName);
                request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "GET";
                request.ContentType = "application/x-www-form-urlencoded";
                var response = (HttpWebResponse)request.GetResponse();
                Stream stream = response.GetResponseStream();
                img = Image.FromStream(stream);
                ImageFileName FileNameImage = new ImageFileName(fileName, img);
                ImageQueue.Enqueue(FileNameImage);
                Console.WriteLine("Count after Enqueue: {0}", ImageQueue.Count);

         }

And another thread takes images from Queue and saves them on destination folder. Here is the code for that:

public static void SaveImage()
    {
        while (true)
        {
            if (!ImageQueue.IsEmpty)
            {
                foreach (var newobject2 in ImageQueue)
                {

                    Image img2 = newobject2.Image;
                    img2.Save("C:\\path" + newobject2.ImageName);
                    ZoomThumbnail = img2;
                    ZoomSmall = img2;
                    ZoomLarge = img2;

                    ZoomThumbnail = GenerateThumbnail(ZoomThumbnail, 86, false);
                    ZoomSmall = GenerateThumbnail(ZoomSmall, 400, false);
                    ZoomLarge = GenerateThumbnail(ZoomLarge, 1200, false);

                    ZoomThumbnail.Save("C:\\path" + newobject2.ImageName + "_Thumb.jpg");
                    ZoomSmall.Save("C:\\path" + newobject2.ImageName + "_ZoomSmall.jpg");
                    ZoomLarge.Save("C:\\path" + newobject2.ImageName + "_ZoomLarge.jpg");
                    ImageFileName imgobject3 = new ImageFileName();
                    ImageQueue.TryDequeue(out imgobject3);
                    Console.WriteLine("Count after Deque: {0}", ImageQueue.Count);

                }


            }


        }

    }

I am calling these two threads from Button_Click() like this:

Thread DownloadThread = new Thread(DownloadImage);
  DownloadThread.Start();
  Thread SaveThread = new Thread(SaveImage);
  SaveThread.Start();

I am getting MemoryFull error whenever queue reaches count of 68. I am not sure how I can avoid that. I have tried using Thread.Sleep to avoid this. For Example, I tried: Thread.Sleep(500) after foreach loop. Whenever I try it inside foreach it works totally fine as at any given point ImageQueue.Count = 1. Where I am getting wrong?

nvoigt
  • 75,013
  • 26
  • 93
  • 142
LearningAsIGo
  • 1,240
  • 2
  • 11
  • 23
  • Do you release the image once you're done with it by calling `Dispose()` etc? If not that's going to eat memory for sure. You should also release your `HttpWebResponse` by calling `Close()` or wrapping it with `using`. – Lloyd Apr 07 '14 at 15:27
  • Where should I Dispose() image? Inside foreach of SaveImage()? – LearningAsIGo Apr 07 '14 at 15:32

5 Answers5

1

You never actually remove your images from the queue. You iterate through them, but you never dequeue them. You also have the problem that when enumerating the queue the enumerable will stop whenever there are currently no more items in the queue. You don't want to do this, you may not be done yet.

What you want to do is use a BlockingCollection instead of a ConcurrentQueue, so that if there are currently no more items you wait for more, rather than stopping. Once you do this, you can use foreach(var item in queue.GetConsumingEnumerable()). This makes all of the needed changes. It removes the items from the queue as you iterate it, and it waits for more items if there are currently none.

In addition to making this change, as is mentioned in Guffa's answer, you need to dispose of your image objects. You can dispose of them at the end of your loop body, once you're done with the image.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • I want to show what I have done using BlockingCollection by sharing code here. But I am not able to. How should I show it to you? – LearningAsIGo Apr 07 '14 at 15:56
  • using BlockingCollection it is giving desired output.. But I am not sure if its correct functionally. I would like to show it to someone who understand these concepts very well. – LearningAsIGo Apr 07 '14 at 16:02
  • That's not what this site is for; this site is for specific questions related to specific problems, not general code reviews. – Servy Apr 07 '14 at 16:03
  • OK. So if I am getting right output, it has to be correct? or there can be a glitch? – LearningAsIGo Apr 07 '14 at 16:04
  • 1
    @DotNetNewBie There may well be a problem but you would need to know that you have a problem, and have some idea of what it is, to ask a question about it on this site. Do some testing, experiment, look for problems. If you find one, and cannot solve it or find a solution after spending some time and effort looking into it, then consider posting here. Since you don't even know *if* you have a problem, you're a *long* ways away from posting a question here. – Servy Apr 07 '14 at 16:07
  • you are misunderstanding my comment. I will explain more so that it is clear. What I want to do is, I want to scale the use of threading by having multiple threads performing downloading image and multiple threads performing save operation. Can it be done using BlockingCollection? – LearningAsIGo Apr 07 '14 at 16:12
  • @DotNetNewBie Yes, it can. – Servy Apr 07 '14 at 16:13
  • *"You never actually remove your images from the queue."* - It seems that you missed the part of the code where the item is removed from the queue. – Guffa Apr 07 '14 at 16:26
  • @Guffa No, he creates a new item in the loop, with no data, and tries to remove *that* from the queue. It will never be in the queue (he just created it after all) so nothing ever ends up being removed from the queue. – Servy Apr 07 '14 at 16:27
  • @Servy: You are mistaken. Just because the reference points to a newly create object, that doesn't mean that the `TryDequeue` method tries to remove that object from the queue. The reference is only used for an output value, any previous value is ignored. – Guffa Apr 07 '14 at 16:32
  • @Guffa Well its still not reliably removing the item processed, given that it's a multithreaded context, so it's still incorrect and needs to be fixed either way. – Servy Apr 07 '14 at 16:35
  • @Guffa so the problem is I am removing the object which is empty(imgobject3) and not the object that is actually in the queue. This is what you are saying right? – LearningAsIGo Apr 07 '14 at 17:08
  • @DotNetNewBie It was, but I was incorrect, as Guffa mentioned, but the code was still incorrect for other related reasons. You were still potentially pulling out the wrong item, and you should still refactor the code as I described, just not for the exact reasons that I described. – Servy Apr 07 '14 at 17:09
  • @Servy I am getting same error with BlockingCollection. – LearningAsIGo Apr 07 '14 at 17:35
  • @DotNetNewBie: The `BlockingCollection` only improves how you handle the threading, it has nothing to do with your memory problem. The solution for that is to dispose all the disposable objects that you create. – Guffa Apr 07 '14 at 19:16
  • I added img2.Dispose(); after ZoomLarge.Save() but it is still giving me same error. When I checked in Task Manager, memory consumed by this application is 1GB+! – LearningAsIGo Apr 07 '14 at 20:28
  • @Servy Due to some errors BlockingCollection was not working but it is working just fine and also I added Dispose as Guffa suggested. Thank you! – LearningAsIGo Apr 08 '14 at 16:57
0

You are creating a lot of Image objects that you don't dispose. You should call Dispose on each of the thumbnails that you create.

Also, the object that you dequeue contains an Image object that you should dispose when you don't need it any more.


Side note: You shouldn't enumerate the items in the queue and deqeue the item inside the loop. Instead use the TryDequeue method in the loop itself:

ImageFileName imgobject2;
while (ImageQueue.TryDequeue(out imgobject2)) {
  ...
}

Another side note: Your code will go into a tight loop when the queue is empty, using a lot of CPU power to repeatedly make the same check very rapidly. You should use a mechanism to suspend the thread until something happens with the queue, or at least sleep for a while before checking the queue again.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • So are you suggesting While loop instead of foreach in SaveImage method? – LearningAsIGo Apr 07 '14 at 15:39
  • @DotNetNewBie: Yes, that is more consistent with the use of a concurrent queue. If you dequeue the item in the loop, there is no relation between the item that you have processed and the item that you dequeue, they just happen to be the same because you have no other code that dequeues items. If you use the `while` instead it becomes thread safe, and you can even have several threads running the method at the same time. – Guffa Apr 07 '14 at 16:20
0

You should dispose after you are done with the image so after the ZoomLarge statement because after that you dont make any use of it anymore. With the Generate statements you create new images.

0

1.It seems that your are keeping a reference to all your images which keeps them in memory, not allowing the garbage collector to collect them.

Also, after you've finished using your objects, you can explictly dispose them, like this:

Image img2 = newobject2.Image;
img2.Dispose();

2. What your are doing is enumerating over all your images everytime you are in a while loop, i am not sure this is what you want. What you should do is try to dequeue an item:

FileImageName myImage;
ImageQueue.TryDequeue(out myImage);

if (myImage != null)
{
// Do work on the image
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
0

You might want to look at the BlockingCollection class (here), which I've successfully used for this kind of "producer-consumer" pattern.

Something (a producer) puts items into the collection, with one or more "consumer" b/g threads taking the next available item(s) out of the collection and doing something with them. You can also configure the number of consumer threads to use (e.g. for a multi-core PC).

Andrew Stephens
  • 9,413
  • 6
  • 76
  • 152