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.
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
}
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
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;
}
}
}
}