GUI не отвечает при получении данных

Мое приложение часто извлекает данные с веб-страницы с помощью WebRequest, но невозможно нажимать кнопки и т. д. во время выборки. Я понял, что мне нужно использовать потоки/фоновый рабочий процесс, но я не могу заставить его работать должным образом; это не делает графический интерфейс более отзывчивым.

Код, к которому я хочу применить какую-то многопоточность, чтобы приложение перестало отвечать на запросы:

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

Редактировать: Спасибо, lc, я пробовал что-то очень похожее на это. Но моя проблема с использованием фонового рабочего такова: как мне вернуть queryResult обратно в функцию, которая вызвала (в моем случае SQLGet, а в вашем случае) StartQuery?

В моем примере возвращаемая строка будет использоваться как локальная переменная в пустоте, внутри которой вызывается строка.

И запросов может быть много одновременно, поэтому я не хочу рисковать присваивать его глобальной переменной.


person Phoexo    schedule 09.07.2009    source источник
comment
Пожалуйста, опубликуйте код, в котором вы начинаете поток и запускаете SQLGet.   -  person John Kugelman    schedule 09.07.2009
comment
На данный момент я не использую потоки, потому что не знаю, как реализовать их так, как я хочу, чтобы они работали. Вот почему я прошу о помощи.   -  person Phoexo    schedule 09.07.2009
comment
Обновил мой ответ, чтобы он соответствовал вашему редактированию.   -  person lc.    schedule 09.07.2009


Ответы (3)


Вот простой пример использования BackgroundWorker. поскольку это относится к вашему коду:

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

Вы также можете разрешить отмену, предоставить сведения об ошибках или предоставить надежную обратную связь по мере получения данных. Взгляните на пример на странице MSDN. Больше подробностей.

Результат вашего запроса будет отображаться в событии BackgroundWorker.RunWorkerCompleted как e.Result (в данном случае я сохранил его как переменную экземпляра). Если вы собираетесь запускать многие из них одновременно, вам понадобится способ различать, какой запрос является каким. Таким образом, вы должны передать методу больше, чем просто строку. Возьмите этот пример:

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
}
person lc.    schedule 09.07.2009
comment
Также; не имеет ли Close() ту же функцию, что и Dispose()? - person Phoexo; 09.07.2009
comment
@Phoexo Вы правы в том, что Close() вызывает Dispose(). Я просто всегда использую или Dispose() IDisposables по привычке, но, видимо, в этом нет необходимости (msdn.microsoft.com/en-us/library/system.io.stream.close.aspx). Извините за путаницу. - person lc.; 09.07.2009
comment
Это не работает должным образом; runworkercompleted не передает никакого значения в args.Result. RunWorkerCompletedEventArgs не содержит значения аргумента. - person Phoexo; 09.07.2009
comment
И это последствия того, что вы не ложитесь спать слишком поздно ночью. Я исправил это (надеюсь), так что попробуйте сейчас. Вы должны передать весь объект QueryArguments обратно в качестве результата. (Или создайте отдельный объект результата, если хотите). - person lc.; 10.07.2009
comment
Чтобы это работало так, как я хотел, мне пришлось создать дополнительный список массивов, в котором хранится результат, и использовать для него NextID в качестве индекса. Отлично сработало, спасибо :D - person Phoexo; 18.07.2009

BackgroundWorker — хорошее решение с некоторой встроенной поддержкой отмены и выполнения. Вы также можете просто использовать HttpWebRequest.BeginGetResponse вместо GetResponse, чтобы инициировать операцию асинхронного веб-запроса. Это оказывается довольно просто, и вы можете настроить обратный вызов прогресса.

Для точного примера того, что вы пытаетесь сделать, см.:

Использование HttpWebRequest для асинхронных загрузок

person Sean Sexton    schedule 09.07.2009

Вот быстрое решение, должно быть легко взять то, что вам нужно.

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;
            }
        }
    }
}
person PostMan    schedule 09.07.2009
comment
Разве вам не нужен вызов Application.DoEvents() в цикле while? В противном случае нет причин запускать его в отдельном потоке... - person lc.; 09.07.2009