none
How can i use Stream CopyTo to a FileStream ? RRS feed

  • Question

  • I have this method:

    public async Task<Image> GetImageAsync(string url)
            {
                var tcs = new TaskCompletionSource<Image>();
                Image webImage = null;
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "GET";
                await Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null)
                    .ContinueWith(task =>
                    {
                        var webResponse = (HttpWebResponse)task.Result;
                        Stream responseStream = webResponse.GetResponseStream();
                        if (webResponse.ContentEncoding.ToLower().Contains("gzip"))
                            responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
                        else if (webResponse.ContentEncoding.ToLower().Contains("deflate"))
                            responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
    
                        if (responseStream != null) webImage = Image.FromStream(responseStream);
                        tcs.TrySetResult(webImage);
                        webResponse.Close();
                        responseStream.Close();
                    });
                return tcs.Task.Result;
            }

    And using it:

    int count = 0;
            void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
            {
                mshtml.HTMLDocument objHtmlDoc = (mshtml.HTMLDocument)webBrowser1.Document.DomDocument;
                string pageSource = objHtmlDoc.documentElement.innerHTML;
    
                if (firsturltonav == true)
                    ParseNumberOfLinks();
    
                string[] hrefs = this.webBrowser1.Document.Links.Cast<HtmlElement>()
                 .Select(a => a.GetAttribute("href")).Where(h => h.Contains(".jpg")).ToArray();
    
                foreach(string a in hrefs)
                {
                    var result = GetImageAsync(a);
                    result.ContinueWith(task =>
                    {
                        try
                        {
                            task.Result.Save(@"C:\images\file" + count + ".jpg");
                            pictureBox1.Image = task.Result;
                            count++;
                            label2.Invoke(new MethodInvoker(delegate { label2.Text = count.ToString(); }));
                            
                        }
                        catch(Exception ee)
                        {
                            string myer = ee.ToString();
                        }
                    }); 
                }
            }

    The problem is that after saved two images to the hard disk i'm getting exception on the line:

    task.Result.Save(@"C:\images\file" + count + ".jpg");

    A generic error occurred in GDI+

    If i'm using try and catch i see in the StackTrace:

    at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams) at System.Drawing.Image.Save(String filename, ImageFormat format) at System.Drawing.Image.Save(String filename) at Extract_Images.Form1.b__2(Task`1 task) in d:\C-Sharp\Extract_Images\Extract_Images\Extract_Images\Form1.cs:line 76

    Line 76 is:

    task.Result.Save(@"C:\images\file" + count + ".jpg");

    Maybe the problem is: That i  Just need somehow to use Stream.CopyTo(), to copy the data to aFileStream, instead of making the Image object from the data. So i can still display the images in pictureBox1...just to read it from the FileStream i create.

    How can i do it ? 

    What i want to do is to display the images i download one by one in the pictureBox1 and also to save the images the to the hard disk. But the saving line make the exception.

    Monday, August 29, 2016 3:25 AM

Answers

All replies

  • I'm pretty sure, that using a FileStream will not help. Just return the response stream, as Task<Stream>. To capture download errors, create a copy of the reponse stream as return value.

    Monday, August 29, 2016 2:37 PM
  • I'm pretty sure, that using a FileStream will not help. Just return the response stream, as Task<Stream>. To capture download errors, create a copy of the reponse stream as return value.

    The whole method GetImageAsync should be Task<Stream> instead Task<Image> ?

    Could you show me how it should be the method and then how to use it in the foreach loop ?

    Monday, August 29, 2016 3:13 PM
  • The issue you're having is because you're closing the response stream. Image.FromStream requires that the stream you pass it (the ResponseStream) remain open for the life of the image. Since you close the stream the Image class will throw an exception.

    You have additional issues with your code though. The second problem is that you are accessing the UI on a non-UI thread. Specifically in your ContinueWith call you are attempting to set the picturebox image but that will be done on a non-UI thread so an exception is going to happen at some point. Interestingly enough, later you use Invoke to update the UI, which would be correct in this case.

    Yet another issue, to me, is that your bothering to assign the image to the picturebox at all. Given that this a for loop only the last image will be set so why bother wasting the time updating the UI anyway.

    I think your code is overly complex for what you're trying to do. I recommend you take a look at async/await instead. Here's a first pass.

    private static Stream GetCompressedStream ( HttpWebResponse response )
    {
        var stream = response.GetResponseStream();
    
        if (response.ContentEncoding.ToLower().Contains("gzip"))
            return new GZipStream(stream, CompressionMode.Decompress);
        else if (response.ContentEncoding.ToLower().Contains("deflate"))
            return new DeflateStream(stream, CompressionMode.Decompress);
    
        return null;
    }
    
    async Task<Image> GetImageAsync ( string url )
    {
        var request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "GET";
                
        //On worker thread
        var response = await request.GetResponseAsync() as HttpWebResponse;
    
        //Back on calling thread
        var responseStream = GetCompressedStream(response);            
        if (responseStream != null)
            return Image.FromStream(responseStream);            
    
        return null;
    }
    
    int count = 0;
    
    async Task webBrowser1_DocumentCompleted ( object sender, WebBrowserDocumentCompletedEventArgs e )
    {
        //mshtml.HTMLDocument objHtmlDoc = (mshtml.HTMLDocument)webBrowser1.Document.DomDocument;
        //string pageSource = objHtmlDoc.documentElement.innerHTML;
    
        //if (firsturltonav == true)
            //  ParseNumberOfLinks();
    
        //string[] hrefs = this.webBrowser1.Document.Links.Cast<HtmlElement>()
            //.Select(a => a.GetAttribute("href")).Where(h => h.Contains(".jpg")).ToArray();
    
    //If there are a lot of links then start all the async work
    //and then foreach the results so you're not waiting on each one foreach (string a in hrefs) { try { //On worker thread var result = await GetImageAsync(a); //Back on UI thread if (result == null) continue; result.Save(@"C:\images\file" + count + ".jpg");
    //Why not do this once after all images are processed? pictureBox1.Image = result; count++; label2.Text = count.ToString(); } catch (Exception ee) {
    //Does this serve any purpose? string myer = ee.ToString(); } } }

    Michael Taylor
    http://www.michaeltaylorp3.net

    • Proposed as answer by Kevin Linq Wednesday, September 7, 2016 3:18 AM
    Monday, August 29, 2016 3:25 PM
    Moderator
  • I'm getting error on this line in the document completed event:

    var result = await GetImageAsync(a);

    On the right side:

    Error 5 The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'. 

    Monday, August 29, 2016 4:16 PM
  • You need to mark the document complete handler as async. Refer to the method declaration in the code I provided.
    Monday, August 29, 2016 4:23 PM
    Moderator
  • Now i'm getting error in the constructor on the line:

    webBrowser1.DocumentCompleted += webBrowser1_DocumentCompleted;


    On the right side:

    Error 1 'System.Threading.Tasks.Task Extract_Images_DocumentReality.Form1.webBrowser1_DocumentCompleted(object, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)' has the wrong return type

    This is the document completed event code now:

    List<Image> images = new List<Image>(); int count = 0; async Task webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { mshtml.HTMLDocument objHtmlDoc = (mshtml.HTMLDocument)webBrowser1.Document.DomDocument; string pageSource = objHtmlDoc.documentElement.innerHTML; if (firsturltonav == true) ParseNumberOfLinks(); string[] hrefs = this.webBrowser1.Document.Links.Cast<HtmlElement>() .Select(a => a.GetAttribute("href")).Where(h => h.Contains(".jpg")).ToArray();

    //If there are a lot of links then start all the async work //and then foreach the results so you're not waiting on each one foreach (string a in hrefs) { //On worker thread var result = await GetImageAsync(a); //Back on UI thread if (result == null) continue; result.Save(@"C:\images\file" + count + ".jpg"); label2.Text = count.ToString(); } ProcessNextLink(); }


    Monday, August 29, 2016 7:04 PM
  • Change your handler to be async void instead of async Task. In general this is a bad idea but in an event handler this is sometimes needed.
    Monday, August 29, 2016 7:18 PM
    Moderator
  • Ok i tried now an use break point and i see that it's all the time return null from both methods GetImageAsync and GetCompressedStream.

    Then in the foreach loop result is null all the time so it's making continue:

     if (result == null)
         continue;

    Monday, August 29, 2016 7:37 PM
  • Step through your code. Most likely GetImageAsync isn't seeing the correct conditions and therefore isn't working.
    Monday, August 29, 2016 7:47 PM
    Moderator