Как обрабатывать возвращаемые значения в асинхронной функции


При работе с API данных, использующими асинхронные вызовы rest (я использую RestSharp.Portable), как лучше всего обрабатывать возвращаемые значения? Поскольку асинхронная функция может возвращать только задачу или задачу ... но у вызывающего абонента нет возможности вернуться к возвращаемому значению ... как API вернет данные обратно вызывающему объекту? Глобальные свойства?

Из того, что я прочитал до сих пор, следует, что функции обратного вызова являются единственным способом взаимодействия с данными ответа?

Возьмем следующий метод например; ранее я не использовал библиотеку async Rest и мог возвращать значение, но после преобразования его в использование RestSharp.Portable, я не вижу способа вернуть значение:

public async Task<EntityResourceDescriptor> GetEntityDescriptor(string entityType)
    {
        TaskCompletionSource<EntityResourceDescriptor> tcs = new TaskCompletionSource<EntityResourceDescriptor>();
        var req = new RestRequest("/qcbin/rest/domains/{domain}/projects/{project}/customization/entities/{entityType}");
        AddDomainAndProject(req);
        req.AddParameter("entityType", entityType, ParameterType.UrlSegment);
        client.ExecuteAsync<EntityResourceDescriptor>(req, (res) =>
            {
                if (res.ResponseStatus == ResponseStatus.Error)
                {
                    tcs.TrySetException(res.ErrorException);
                }
                else
                {
                    tcs.SetResult(res.Data);
                }
            }
        );
        return tcs.Task;
    }

Здесь все, что я могу сделать, это вернуть задачу, но у вызывающего все еще нет способа добраться до данных ответа, или я упускаю что-то очевидное? Может ли вызывающий абонент подписаться на событие, которое будет запущено в Task.Завершено и т. д.?

Я очень нечетко представляю себе эту асинхронную концепцию. Есть ли примеры написания переносимого текста API данных?

1 2

1 ответ:

Я думаю, что вам действительно нужно сделать шаг назад и прочитать о том, как использовать ключевые слова async и await. Помимо всего прочего, вам нужно будет понять некоторые из волшебств компилятора, которые происходят за кулисами при кодировании методов async.

Вот хорошее место для начала: асинхронное программирование с асинхронностью и ожиданием.

Что касается вашего вопроса, то в разделеReturn Types and Parameters говорится следующее:

Вы указываете Task<TResult> как возвращает тип, если метод содержит оператор Return (Visual Basic) или return (C#), который задает операнд типа TResult.

Затем он дает следующий пример кода:

// Signature specifies Task<TResult>
async Task<int> TaskOfTResult_MethodAsync()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}
Обратите внимание, что, несмотря на тип возвращаемого метода Task<int>, оператор return просто возвращает int, а не Task<int>. Это в основном потому, что существует некоторая магия компилятора, которая делает это законным только в методах async.

Не желая входить во все кроме того, вы должны знать, что вызывающий метод async обычно должен делать это с помощью ключевого слова await, которое знает, как обращаться с возвращаемыми значениями Task или Task<TResult> и автоматически разворачивает фактическое ожидаемое возвращаемое значение для вас прозрачным образом (больше магии компилятора за кулисами).

Итак, для приведенного выше примера, вот один из способов назвать его:

int intValue = await TaskOfTResult_MethodAsync(); // Task<int> is automatically unwrapped to an int by the await keyword when the async method completes.

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

Task<int> t = TaskOfTResult_MethodAsync();
// perform other work here
int intValue = await t; // wait for TaskOfTResult_MethodAsync to complete before continuing.

Надеюсь, это даст вам общее представление о том, как передавать значения обратно из асинхронного метода.

Для вашего конкретного примера, я не знаком с RestSharp (никогда не использовал его). Но из того немногого, что я прочитал, я думаю, что вы захотите использовать client.ExecuteTaskAsync<T>(request) вместо client.ExecuteAsync<T>(request, callback), чтобы лучше вписаться в модель async-await.

Я думаю, что ваш метод будет выглядеть примерно так:

public async Task<EntityResourceDescriptor> GetEntityDescriptor(string entityType)
{
    var req = new RestRequest("/qcbin/rest/domains/{domain}/projects/{project}/customization/entities/{entityType}");
    AddDomainAndProject(req);
    req.AddParameter("entityType", entityType, ParameterType.UrlSegment);
    var res = await client.ExecuteTaskAsync<EntityResourceDescriptor>(req);

    if (res.ResponseStatus == ResponseStatus.Error)
    {
        throw new Exception("rethrowing", res.ErrorException);
    }
    else
    {
        return res.Data;
    }
}

Ваш код вызова тогда это выглядело бы так:

EntityResourceDescriptor erd = await GetEntityDescriptor("entityType");
Я надеюсь, что вам удастся заставить это работать. Но опять же, не забудьте прочитать документацию о стиле программирования async-await. Это очень аккуратно, как только вы обернете свою голову вокруг магии компилятора, которая делается для вас. Но так легко заблудиться, если у вас нет времени, чтобы действительно понять, как это работает.