Нужно обернуть голову вокруг асинхронных операций - PullRequest
4 голосов
/ 08 ноября 2011

У меня есть пользовательский элемент управления, и у меня есть интерфейс, который этот элемент управления предоставляет своим пользователям.

public interface ILookupDataProvider
{
    string IdColumnName { get; }

    IEnumerable<IDataColumn> Metadata { get; set; }

    void GetDataAsync(string parameters, 
        Action<IEnumerable<object>> onSuccess, Action<Exception> onError);
}

Итак, это моя попытка выставить асинхронную операцию в GetDataAsync

Но я не знаю, как реализовать этот метод в моем классе, который реализует интерфейс. Я понимаю эту часть, поскольку у меня есть метод, который будет выполняться, и тогда будет вызван делегат onCompletion, onSucess или onError.

Может кто-нибудь помочь с синтаксисом о том, как их написать?

EDIT:

Это 4.0, и я не могу использовать await команды

РЕДАКТИРОВАТЬ 2:

Я использую инфраструктуру DevForce для загрузки данных, но ради этого примера - давайте сделаем сервис WCF, например. Как бы я обернул вызов службы WCF в моей реализации интерфейса?

Кроме того, как вы думаете, можно ли создавать такой интерфейс для представления асинхронной операции? Вы могли бы сделать это по-другому, например, с событием?

Ответы [ 3 ]

6 голосов
/ 08 ноября 2011

Основная идея здесь заключается в том, что запрос будет выполняться в фоновом потоке. Как только ваша операция будет завершена, вы будете использовать обратные вызовы onSuccess и onError для сообщения о новых значениях. Например

void GetDataAsync(
  string parameters, 
  Action<IEnumerable<object>> onSuccess, 
  Action<Exception> onError) {

  WaitCallback doWork = delegate { 
    try { 
      IEnumerable<object> enumerable = GetTheData(parameters);
      onSuccess(enumerable);
    } catch (Exception ex) {
      onError(ex);
    }
  };

  ThreadPool.QueueUserWorkItem(doWork, null);
}
3 голосов
/ 08 ноября 2011

Вы действительно не хотите использовать этот шаблон:

void GetDataAsync(string parameters, 
    Action<IEnumerable<object>> onSuccess, Action<Exception> onError);

Вместо этого вы хотите использовать это:

Task GetDataAsync(string parameters);

При возврате Task, вы возвращаете экземпляр, который представляет асинхронную единицу работы.Оттуда потребитель API может выбрать вызов ContinueWith и решить, что делать, когда он преуспевает, или если есть ошибка.

Однако, есть недостаток дизайнав вашем примере.В методе с именем GetDataAsync я ожидаю, что данные будут возвращены.Это не проблема, вы можете просто изменить подпись на:

Task<MyData> GetDataAsync(string parameters);

Теперь это возвращает Task<T>, который вы можете использовать Result свойство или получить результат (он заблокируется, если задача не выполнена), или вы можете снова использовать метод ContinueWith для обработки данных после выполнения асинхронной операции.

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

Task<MyData> GetDataAsync(string parameters, 
    CancellationToken cancellationToken);

Опять же, ContinueWith позволит вам указать действие, которое нужно предпринять при отмене.

Обратите внимание, что именно таким образом в настоящее время моделируются методы, использующие await и async в Async CTP ;они возвращаются Task или Task<T>;если вы сделаете то же самое в своем коде, вы будете готовы к тому, что эти функции языка будут включены.

1 голос
/ 08 ноября 2011

Чтобы использовать задачи, вы можете использовать это так.Единственная хитрость, которую нужно запомнить, - выполнить обратный вызов в потоке пользовательского интерфейса, что достигается с помощью TaskScheduler.FromCurrentSynchronizationContext (), чтобы вы могли обновить свой пользовательский интерфейс или отобразить окно сообщения, если что-то пошло не так.

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

private void button1_Click(object sender, EventArgs e)
{
    GetDataAsync("www.data.com").ContinueWith(result =>
        {
            if (result.Exception != null)
            {
                MessageBox.Show(this, "Error: {0}" + result.Exception, "Error");
            }
            else
            {
                foreach (var obj in result.Result)
                {
                    textBox1.Text += obj.ToString();
                }
            }
        },
        TaskScheduler.FromCurrentSynchronizationContext()
        );

}

Task<IEnumerable<object>> GetDataAsync(string parameters)
{
    return Task<IEnumerable<object>>.Factory.StartNew(() =>
    {
        Thread.Sleep(500);
      //  throw new ArgumentException("uups");
        // make wcf call here
        return new object[] { "First", "second" };
    });
}
...