ExecuteAsync RestSharp, чтобы разрешить backgroundWorker CancellationPending c#

Я новичок в C#, RestSharp и многопоточности, поэтому вот что я пытаюсь сделать:
Я сделал программу, которая позволит мне загружать фотографии в tumblr, и до сих пор загрузка работает. Теперь мне нужно, чтобы кнопка остановки работала, что, как я полагаю, означает, что я должен использовать ExecuteAsync() вместо Execute(). У меня также есть код, помещенный в фоновый рабочий процесс, например:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    if (backgroundWorker1.CancellationPending)
    {
        e.Cancel = true;
        MessageBox.Show("You pressed Cancel.");
    }
    else
    {
    var restClient = new RestClient("http://tumblr.com/api/write");
    foreach (string item in queueBox.Items)
    {
        var request = new RestRequest(Method.POST);
        request.RequestFormat = DataFormat.Json; //I don't know if this line is necessary
        request.AddParameter("email", usernameBox.Text);
        request.AddParameter("password", passwordBox.Text);
        request.AddParameter("type", "photo");
        request.AddFile("data", FolderName + "\\" + item);
        RestResponse response = restClient.Execute(request);
        doneBox.Invoke(new UpdateTextCallback(this.UpdateText),
            new object[] { item });
    }
    }
}

Я считаю, что настроил это правильно. Когда я нажимаю upload, он соответственно переходит на else. Однако я думаю, что RestResponse response = restClient.Execute(request); это блокировка, которая не позволяет моему коду продолжать проверку флага.

Вот как я пытаюсь отменить это.

public void stopButton_Click(object sender, EventArgs e)
{
    doneBox.Items.Add("You pressed the stop button.");
    backgroundWorker1.WorkerSupportsCancellation = true;
    backgroundWorker1.CancelAsync();
}

Кроме того, если это актуально, у меня есть:

public delegate void UpdateTextCallback(string item);, что позволяет мне вызывать UpdateText и FinishedText, как показано выше в backgroundWorker1_DoWork.



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

А еще, я открыт для предложений, если вы видите какую-то неэффективность в моем коде или что-то еще, я буду рад принять ваши предложения.

Спасибо.


person Anteara    schedule 23.03.2012    source источник
comment
Вероятность того, что вы увидите отмену в начале DoWork, равна нулю. Переместите тест внутрь цикла. Может уже достаточно хорошо.   -  person Hans Passant    schedule 23.03.2012


Ответы (1)


Здесь есть несколько потенциальных проблем.

Во-первых, вы, кажется, пытаетесь получить доступ к элементу пользовательского интерфейса из своего фонового потока (а также открыть MessageBox). Это может вызвать исключение CrossThread*.

Во-вторых, ваш код должен выглядеть примерно так:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    var restClient = new RestClient("http://tumblr.com/api/write");
    foreach (string item in queueBox.Items)
    { 
        //This should be inside the foreach 
        //as it is your loop that will check for cancel. 
        //Your code is procedural once it is in the backgroundworker
        //so it would never return to the spot you had it
        if (backgroundWorker1.CancellationPending)
        {
            e.Cancel = true;
            //MessageBox.Show("You pressed Cancel.");
            //Removed this to the background worker completed method below
            //This avoids any UI cross thread exceptions
            return;
        }
        var request = new RestRequest(Method.POST);
        //I believe Json is default for Restsharp, but you would have to play with it
        request.RequestFormat = DataFormat.Json; //I don't know if this line is necessary
        request.AddParameter("email", usernameBox.Text);
        request.AddParameter("password", passwordBox.Text);
        request.AddParameter("type", "photo");
        request.AddFile("data", FolderName + "\\" + item);
        //If you just pass in item to the below Func, it will be a closure
        //Meaning, any updates in the loop will propogate into the Action
        var newItemToAvoidClosure = item;
        //To use Async, you set up the callback method via a delegate
        //An anonymous method is as good as any here
        restClient.ExecuteAsync(request, 
            response=>
            { 
                //Maybe you should do something with the response?
                //Check the status code maybe?
                doneBox.Invoke(new UpdateTextCallback(this.UpdateText),
                    new object[] { newItemToAvoidClosure });
            }
        );
    }
}

Подключите метод RunWorkerCompleted вашего фонового работника к этому и выполните всю свою пост-обработку здесь:

private void backgroundWorker1_RunWorkerCompleted(object sender,
    RunWorkerCompletedEventArgs e)
{
    if(e.Cancelled)
        MessageBox.Show("You pressed Cancel"
}

Кроме того, если вы используете версию 4.0+, я предлагаю изучить библиотеку параллельных задач< /а>. Это может сделать ваш код намного чище IMO :).

Наконец, примечание о приведенном выше коде: фоновый рабочий процесс может завершиться до завершения всех вызовов Rest. Это, вероятно, будет выполняться довольно быстро, и вызовы будут продолжаться, поскольку они не могут быть отменены таким образом (фоновый рабочий уже будет завершен) (но я считаю, что есть способ сделать это для каждого вызова Rest). Итак, мне кажется, что реальная проблема заключалась в том, что проверка отмены была в неправильной части кода (заметьте, я переместил ее внутрь цикла, чтобы ее можно было проверять после обработки каждого файла). Вы уже работаете в фоновом потоке, поэтому мне кажется, что нет смысла вызывать еще один асинхронный режим (если только вы не намерены разгрузить цикл отправляемых данных, который затем разгружает фактическую отправку).

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

* Это может быть не так, поскольку вы только обращаетесь к элементу пользовательского интерфейса, а не обновляете его, и вы сказали, что эта часть работает (хотя, вероятно, это будет для MessageBox)

person Justin Pihony    schedule 23.03.2012
comment
Спасибо, когда я пытаюсь использовать этот код, я получаю A local variable named response cannot be declared in this scope because it would give a different meaning to 'response' which is already used in a 'parent' or curren't scope to denote something else. Если я изменю его на ` RestResponse _response = restClient.ExecuteAsync (запрос, ` он удаляет эту ошибку, но теперь я получаю Cannot implicitly convert type RestSharp.RestRequestAsyncHandle' to 'RestSharp.RestResponse' - person Anteara; 23.03.2012
comment
Я изменил его на var _response, но на самом деле он не загружается, он просто печатает UpdateText в doneBox. Мол, я знаю, что он даже не пытается загрузиться, потому что он проходит скрипт буквально за 1 секунду. - person Anteara; 23.03.2012
comment
Судя по MessageBox.Show(response.ResponseStatus.ToString());, я получаю Timed out за каждый запрос. - person Anteara; 23.03.2012
comment
@Anteara Я обновил свой ответ, чтобы удалить сохранение ответа на асинхронный вызов (поскольку он вам здесь действительно не понадобится). Кроме того, я предложил использовать RunWorkerCompleted, чтобы лучше справляться с потенциальными ошибками обработки пользовательского интерфейса. Наконец, вы можете установить для тайм-аута своего клиента более высокое значение (я не уверен в значении по умолчанию), чтобы решить текущую проблему тайм-аута. Однако, похоже, это решило ваш первоначальный вопрос. Если это правда, голоса и принятые ответы всегда приветствуются :) - person Justin Pihony; 23.03.2012
comment
Спасибо, у меня возникли проблемы с поиском документации о том, как установить время ожидания для ExecuteAsync. Я нашел Execute документацию по настройке тайм-аута, но не ExecuteAsync. Я чувствую, что документация для restSharp ограничена, или, возможно, я ищу не в том месте. Помогите разобраться как настроить таймаут? Спасибо. - person Anteara; 24.03.2012
comment
@Anteara Ну, это с открытым исходным кодом, поэтому вы можете просто прочитать код :) Я так и сделал, и тайм-аут должен быть настроен так же, как и для синхронного действия ... RestClient.Timeout Если у вас есть старый код, это была ошибка, которая не была объединена до ~ 1 года назад, хотя - person Justin Pihony; 24.03.2012
comment
Это нелепо, я не понимаю, почему это не работает.var restClient = new RestClient("http://tumblr.com/api/write"); restClient.Timeout = 10000; Ни это, ни response => { restClient.Timeout = 10000; не работают. Он просто ничего не делает, нет никакой разницы между тем, что он есть, и тем, что его нет. Неважно, большое или маленькое число. - person Anteara; 24.03.2012
comment
Базовый запрос использует HttpWebRequest. msdn.microsoft.com/en-us/library/ Указывает, что время ожидания указано в миллисекундах, поэтому вы устанавливаете время ожидания только на 10 секунд. - person Justin Pihony; 24.03.2012
comment
Я знаю, как я уже сказал, не имеет значения, насколько большое или маленькое число, я также установил его на 100 000, и, как я уже сказал, оно делает то же самое. Тем не менее, я попытался отправить простой обычный (в основном, обычный текстовый пост) запрос в API tumblr, и это было успешно, что означает, что, возможно, я неправильно читаю файл? - person Anteara; 24.03.2012
comment
Возможно. Проверьте Fiddler, чтобы увидеть, что на самом деле отправляется? Другим предложением было бы установить 60-секундный сон перед возвращением фонового рабочего. Просто чтобы убедиться, что это не из-за уничтожения экземпляров ... в чем я сомневаюсь, поскольку они должны быть в другом контексте, но никогда не помешает попробовать все при отладке ... ничего не предполагайте. Похоже, что это может быть файл ... удачи, и я бы предложил открыть новый вопрос, если эта проблема не исчезнет :) Возможно, кто-то сталкивался с этим в прошлом. - person Justin Pihony; 24.03.2012
comment
Спасибо, я ценю вашу помощь. Я искал источник и заставил его работать, выполнив следующие действия: byte[] byteArray = File.ReadAllBytes(FolderName + "\\" + item); и request.AddFile("data", byteArray, FolderName + "\\" + item); Честно говоря, я не знаю, почему это исправляет это, но это так :D. - person Anteara; 24.03.2012
comment
@Anteara Хммм, только что посмотрел на код, и похоже, что он должен работать ... посмотри дальше :) - person Justin Pihony; 24.03.2012
comment
Я предполагаю, что это может быть связано с StreamReader? Определенно интересно. Но ваш рабочий код получает данные по-другому... это объясняет, почему один работает, а другой нет. - person Justin Pihony; 24.03.2012