views:

376

answers:

3

My application often fetch data from a webpage using WebRequest, but it isn't possible to click buttons etc while it's fetching. I've understood that I have to use threads/a backgroundworker, but I can't get it to work properly; it doesn't make the GUI more respondable.

The code I want to apply some kind of threading on, so that it stops making my application unresponding:

public string SQLGet(string query)
{
    string post = "q=" + query;
    WebRequest request = WebRequest.Create("http://test.com");
    request.Timeout = 20000;
    request.Method = "POST";
    byte[] bytes = Encoding.UTF8.GetBytes(post);
    request.ContentType = "application/x-www-form-urlencoded";
    request.ContentLength = bytes.Length;

    Stream requestStream = request.GetRequestStream();
    requestStream.Write(bytes, 0, bytes.Length);
    requestStream.Close();

    WebResponse response = request.GetResponse();
    requestStream = response.GetResponseStream();
    StreamReader reader = new StreamReader(requestStream);
    string ret = reader.ReadToEnd();

    reader.Close();
    requestStream.Close();
    response.Close();

    return ret;
}


Edit: Thank you, lc, I had tried something pretty similar to that. But my problem with using the backgroundworker like that is; how do I get the queryResult back to the function which called (in my case SQLGet, and in your case) StartQuery?

In my example, the returned string is going to be used as a local variable in the void the string is called inside.

And there may be many queries at the same time, so I don't want to risk assigning it to a global variable.

+5  A: 

Here's a simple example of how to use the BackgroundWorker as it applies to your code:

private void StartQuery(string query)
{
    BackgroundWorker backgroundWorker1 = new BackgroundWorker();
    backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
    backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
    backgroundWorker1.RunWorkerAsync(query);
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{   
    e.Result = SQLGet((string)e.Argument);
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    queryResult = (string)e.Result;
}

You may also wish to allow cancellation, provide error details, or provide robust feedback as it fetches data. Take a look at the example on the MSDN page for more details.

The result of your query will show up in the BackgroundWorker.RunWorkerCompleted event as e.Result (I've stored it as an instance variable in this case). If you're going to run many of these at the same time, you'll need a way to differentiate which query is which. So you should pass more than just a string to the method. Take this example:

private int NextID = 0;

private struct QueryArguments
{
    public QueryArguments()
    {
    }

    public QueryArguments(int QueryID, string Query)
        : this()
    {
        this.QueryID = QueryID;
        this.Query = Query;
    }

    public int QueryID { get; set; }
    public string Query { get; set; }
    public string Result { get; set; }
}

private int StartQuery(string query)
{
    QueryArguments args = new QueryArguments(NextID++, query);

    BackgroundWorker backgroundWorker1 = new BackgroundWorker();
    backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
    backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
    backgroundWorker1.RunWorkerAsync(args);

    return args.QueryID;
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{   
    QueryArguments args = (QueryArguments)e.Argument;
    args.Result = SQLGet(args.Query);
    e.Result = args;
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    QueryArguments args = (QueryArguments)e.Result;
    //args.Result contains the result

    //do something
}
lc
Thank you. Question updated.
Phoexo
Also; hasn't Close() the same function as Dispose()?
Phoexo
@Phoexo You're right about Close() calling Dispose(). I just always "using" or Dispose() IDisposables out of habit, but apparently it's not necessary (http://msdn.microsoft.com/en-us/library/system.io.stream.close.aspx). Sorry for the confusion.
lc
This isn't working properly; the runworkercompleted isn't passing any value to the args.Result. The RunWorkerCompletedEventArgs does not contain any Argument-value.
Phoexo
And these are the consequences of staying up too late at night. I've fixed it (I hope) so try now. You have to pass the entire QueryArguments object back as the result. (Or create a separate result object if you'd rather).
lc
To make this work the way I wanted, I had to create an extra arraylist where the result is stored, and use the NextID as index for it. Worked great, thanks :D
Phoexo
+2  A: 

Here is a quick solution, should be easy to take what you need from it.

using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;

namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            BackgroundWorker b = new BackgroundWorker();
            b.DoWork += new DoWorkEventHandler(b_DoWork);
            b.RunWorkerCompleted += new RunWorkerCompletedEventHandler(b_RunWorkerCompleted);
            b.RunWorkerAsync("My Query");

            while(b.IsBusy)
            {

            }
            Console.ReadLine();
        }

        static void b_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if(e.Result is string)
            {
                Console.WriteLine((string)e.Result);
            }
        }

        static void b_DoWork(object sender, DoWorkEventArgs e)
        {
            if (e.Argument is string)
            {
                string post = "q=" + (string) e.Argument;
                WebRequest request = WebRequest.Create("http://test.com");
                request.Timeout = 20000;
                request.Method = "POST";
                byte[] bytes = Encoding.UTF8.GetBytes(post);
                request.ContentType = "application/x-www-form-urlencoded";
                request.ContentLength = bytes.Length;
                Stream requestStream = request.GetRequestStream();
                requestStream.Write(bytes, 0, bytes.Length);
                requestStream.Close();
                WebResponse response = request.GetResponse();
                requestStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(requestStream);
                string ret = reader.ReadToEnd();
                reader.Close();
                requestStream.Close();
                response.Close();
                e.Result = ret;
            }
        }
    }
}
PostMan
Don't you want an Application.DoEvents() call in your while loop? Otherwise there's no reason to run it in a separate thread...
lc
It's more to show the layout of the code, and how it works.
PostMan
+3  A: 

BackgroundWorker is a good solution, with some built-in support for cancellation and progress. You can also just use HttpWebRequest.BeginGetResponse, rather than GetResponse, to initiate an asynchronous web request operation. This ends up being quite simple and you can set up a progress callback.

For an exact example of what you're trying to do, see:

Using HttpWebRequest for Asynchronous Downloads

Sean Sexton