0

I'm having some trouble accessing the UI from an another thread. I understand the basics on cross-threading limitations, but I can't seem to write the code that will work. More specifically, I can't access the ListView from a static method (thread).

I'm trying to make it work with backgroundWorker.

Here's my code:

private void start_Click(object sender, EventArgs e)
{
    ServicePointManager.DefaultConnectionLimit = 20;
    var tasks = new List<Task<int>>();

    foreach (ListViewItem item in listView1.Items)
    {
        string currentUrl = item.SubItems[1].Text;
        int i = item.Index;
        tasks.Add(Task.Factory.StartNew(() => { return GetWebResponse(currentUrl, i); }));
    }
    Task.WaitAll(tasks.ToArray());
}

private static int GetWebResponse(string url, int itemIndex)
{
    int statusCode = 0;
    HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);           

    Task<HttpWebResponse> responseTask = Task.Factory.FromAsync<HttpWebResponse>(httpWebRequest.BeginGetResponse, asyncResult => (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult), null);

    backgroundWorker = new BackgroundWorker();
    backgroundWorker.WorkerReportsProgress = true;
    backgroundWorker.WorkerSupportsCancellation = true;
    backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
    backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
    backgroundWorker.RunWorkerAsync();


    return statusCode;
}

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    listView1.Items[0].ImageKey = "green";
}

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    while (!e.Cancel)
    {
        Thread.Sleep(5000);
        backgroundWorker.ReportProgress(0);
    }
}

This code doesn't work because backgroundWorker_DoWork and backgroundWorker_ProgressChanged are not static, but if I make them static then I can't access listView1

EDIT: Got it working. Code below for review

public delegate void delUpdateListView(int itemIndex, int statusCode);

public Form1()
{
    InitializeComponent();
}

private void start_Click(object sender, EventArgs e)
{

    ServicePointManager.DefaultConnectionLimit = 20;
    var tasks = new List<Task<int>>();

    foreach (ListViewItem item in listView1.Items)
    {
        string currentUrl = item.SubItems[1].Text;
        int i = item.Index;
        tasks.Add(Task.Factory.StartNew(() => {
            //return GetWebResponse(currentUrl, i);

            int statusCode = 0;
            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(currentUrl);

            Task<HttpWebResponse> responseTask = Task.Factory.FromAsync<HttpWebResponse>(httpWebRequest.BeginGetResponse, asyncResult => (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult), null);
            statusCode = (int)responseTask.Result.StatusCode;

            object[] invParams = new object[2];
            invParams[0] = i;
            invParams[1] = statusCode;

            if (InvokeRequired)
            {
                BeginInvoke(new delUpdateListView(UpdateListView), invParams);
            }
            else
            {
                Invoke(new delUpdateListView(UpdateListView), invParams);
            }


            return statusCode;

        }));
    }
    Task.WaitAll(tasks.ToArray());
}

public void UpdateListView(int itemIndex, int statusCode) {
    listView1.Items[itemIndex].ImageKey = "green";
}
Cornwell
  • 3,304
  • 7
  • 51
  • 84
  • Possible duplicate of [How to update the GUI from another thread in C#?](http://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c) – Maximilian Riegler Apr 22 '16 at 10:00

1 Answers1

1

I see several problems here:

1) I don't see why GetWebResponse needs to be static. The easiest solution would be to make it an instance method.

2) Why are you using the background worker anyway?

3) It doesn't make much sense to use Tasks and then wait for them to finish right after you spawn them. This blocks your application where it should be responsive.

As for 3): To keep the UI responsive and updatable, disable everything the user may not click before spawning the tasks, add a continuation action to each task that re-enables the UI components. The task may update the list using the usual Invoke calls.

Thorsten Dittmar
  • 55,956
  • 8
  • 91
  • 139
  • Thanks. That helped me getting it working. Could you please check my Edit to see if it follows the best practices. I've added the Invoke calls you mentioned – Cornwell Apr 22 '16 at 10:21
  • 1
    Looks better :-) You're using an `Invoke` call in any case in your code. The code should be like `if (InvokeRequired) DoStuffWithInvoke; else DoStuffDirectlyWithoutInvoke;`. And you can use an anonymous delegate like `Invoke((Action)delegate() { UpdateListView(i, statusCode); });` and don't need a delegate type then. – Thorsten Dittmar Apr 22 '16 at 10:23