views:

218

answers:

1

I have two exceptions here. Not sure why they occur because I use Form.Invoke to run UI updates on the UI thread. So first,

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Xml;
using System.Windows.Forms;

namespace Toplr
{
    using System.Collections.Specialized;
    using System.Xml.XPath;
    using System.Xml.Linq;
    using System.Text;
    using System.ServiceModel.Web;
    using System.ServiceModel.Syndication;
    using System.Net;
    using System.Web;
    using System.Xml.Schema;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;

    public partial class ToplrForm : Form
    {
        private readonly Uri SearchBase = new Uri(@"http://www.twine.com/feed/atom/entries/");

        private readonly UriTemplate SearchTemplate = new UriTemplate(@"search?type={type}&author={author}");

        public ToplrForm()
        {
            InitializeComponent();
            Exiting = false;
            TaskContext = new TaskManager();
            Items = new AsyncBindingList<Twine>(this);
            twineBindingSource.DataSource = Items;
        }

        private void ToplrForm_Load(object sender, EventArgs e)
        {
        }

        private readonly TaskManager TaskContext;

        private readonly AsyncBindingList<Twine> Items;

        private bool Exiting;

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Close()");
            Close();
        }

        private void ToplrForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            MessageBox.Show("Exiting = tru");
            Exiting = true;
            //TaskContext.Dispose();
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var sfd = new SaveFileDialog()
            {
                ValidateNames = true
            };
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                using (var xtw = new XmlTextWriter(sfd.FileName, Encoding.UTF8))
                {
                    var xw = XmlWriter.Create(xtw);
                    xw.WriteStartDocument();
                    xw.WriteStartElement("opml");
                    xw.WriteAttributeString("version", "1.1");
                    xw.WriteStartElement("head");
                    xw.WriteElementString("title", userNameComboBox.Text);
                    xw.WriteEndElement();
                    xw.WriteStartElement("body");
                    foreach (var row in twineDataGridView.SelectedRows)
                    {
                        var twine = (Twine)((DataGridViewRow)row).DataBoundItem;
                        if (twine != null)
                        {
                            xw.WriteStartElement("outline");
                            xw.WriteAttributeString("text", twine.Title);
                            xw.WriteAttributeString("type", "link");
                            xw.WriteAttributeString("url", twine.HtmlAddress);
                            xw.WriteStartElement("outline");
                            xw.WriteAttributeString("text", twine.Title);
                            xw.WriteAttributeString("type", "atom");
                            xw.WriteAttributeString("url", twine.AtomAddress);
                            xw.WriteEndElement();
                            xw.WriteEndElement();
                        }
                    }
                    xw.WriteEndElement();
                    xw.WriteEndElement();
                    xw.WriteEndDocument();
                    xw.Close();
                }
            }
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Copyright (C) 2009 Bent Rasmussen");
        }

        private void accessButton_Click(object sender, EventArgs e)
        {
            var user = userNameComboBox.Text;
            Task.Create(x => ProcessAccount(user));
        }

        public void ProcessAccount(string user)
        {
            this.Invoke((Action)(() =>
            {
                userNameComboBox.Enabled = false;
                accessButton.Enabled = false;
                toolStripStatusLabel1.Text = "Processing...";
            }));

            var param = new NameValueCollection();
            param.Add("type", "Twine");
            param.Add("author", user);
            var source = SearchTemplate.BindByName(SearchBase, param);

            var wc = new WebClient();

            using (var feedStream = wc.OpenRead(source))
            {
                var reader = XmlReader.Create(feedStream);
                var feed = SyndicationFeed.Load(reader);
                int c = 0, i = 0;

                foreach (var item in feed.Items)
                {
                    this.Invoke((Action)(() =>
                    {
                        toolStripProgressBar1.Increment(1);
                        toolStripStatusLabel1.Text = "Processing...";
                    }));

                    if (item.Links.Count != 0)
                    {
                        //try
                        {
                            ProcessTwine(item);
                            i++;
                        }
                        //catch (Exception)
                        {
                            c++;
                        }
                    }
                    if (Exiting)
                        break;
                }
            }

            this.Invoke((Action)(() =>
            {
                userNameComboBox.Enabled = true;
                accessButton.Enabled = true;
            }));
        }

        private Twine ProcessTwine(SyndicationItem item)
        {
            var result = new Twine();
            result.Title = item.Title.Text;
            result.HtmlAddress = item.Links[0].Uri.ToString();
            result.AtomAddress = "";

            var wc = new WebClient();
            var data = wc.DownloadData(result.HtmlAddress);

            var stream = new MemoryStream(data);
            var readerSettings = new XmlReaderSettings()
            {
                ProhibitDtd = false,
                ValidationType = ValidationType.None,
                ValidationFlags = XmlSchemaValidationFlags.None,
            };
            var reader = XmlReader.Create(stream, readerSettings);
            var doc = XDocument.Load(reader);
            var htmlNs = (XNamespace)"http://www.w3.org/1999/xhtml";
            var root = doc.Root;
            var atom = from r in root.Descendants(htmlNs + "head").Descendants(htmlNs + "link")
                       where r.Attribute("rel").Value == "alternate" && r.Attribute("type").Value == "application/atom+xml"
                       select r.Attribute("href");
            foreach (var e in atom)
            {
                if (e.Value != "")
                {
                    result.AtomAddress = e.Value;
                    this.BeginInvoke((Action)(() =>
                    {
                        Items.Add(result);
                        toolStripProgressBar1.Increment(1);
                    }));
                }
                break;
            }

            return result;
        }
    }
}

This triggers the exception "Cannot access a disposed object" on this fragment

            this.Invoke((Action)(() =>
            {
                toolStripProgressBar1.Increment(1);
                toolStripStatusLabel1.Text = "Processing...";
            }));

If this fragment is commented out, I run into the next problem - a TargetInvocationException on Program level.

The inner exception of this is an InvalidOperationException.

The code is quite simple, so it should not be hard to implement this, I just new a few hints to move on.

Visual Studio project files.

+2  A: 

If the user hits the exit button, the Close() method is called, and the UI starts getting torn down. However, your worker code keeps running and attempts to update the UI, which it can no longer do.

If you centralise all those invoke calls:

public void UpdateUI(Action action) {
    if(!Exiting) this.Invoke(action);
}

you can call:

UpdateUI(() =>
        {
            toolStripProgressBar1.Increment(1);
            toolStripStatusLabel1.Text = "Processing...";
        });

(etc - all the this.Invoke calls should use UpdateUI instead) and it should work. Also, make Exiting volatile.

Marc Gravell
Thanks Marc, that's a good idea!It doesn't quite solve the fundamental problem though. I still get the InvalidOperationException.
Bent Rasmussen
I am not sure if it has anything to do with the fact that I'm using Parallel Extensions. The new .Net 4.0 version will have cancellation, I'll definitely also be using that, but that's besides the point.
Bent Rasmussen
Also, with respect to the disposing problem - why would that happen, considering that in practice I have not closed the application, it happens when it's running. All I've basically done is press the button to start the scan.
Bent Rasmussen
Curious; I wonder if there is something else (external to the code shown) that is the problem - an event handler, perhaps?
Marc Gravell
I don't think so, but the full project can be gotten here, updated with your suggestions, Marc:http://dl.getdropbox.com/u/797094/Toplr.zip
Bent Rasmussen
as an aside - the CTP license has expired; I don't think anyone is going to come checking, but... I can't see any obvious problems, I'm afraid.
Marc Gravell
Thanks anyway, so maybe I should just try with a background worker instead and see what happens.
Bent Rasmussen
I just tried using BackgroundWorker instead. Same problem. Hmm!
Bent Rasmussen